Skip to content

Commit 630e99c

Browse files
committed
Initial commit: Swift plugin for subtree
0 parents  commit 630e99c

File tree

10 files changed

+451
-0
lines changed

10 files changed

+451
-0
lines changed

.github/workflows/build.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by swift-plugin-generator 0.1.0
2+
name: Build and Test
3+
4+
on:
5+
pull_request:
6+
branches: [main, master]
7+
workflow_dispatch:
8+
9+
jobs:
10+
build:
11+
strategy:
12+
matrix:
13+
os: [macos-latest, ubuntu-latest]
14+
runs-on: ${{ matrix.os }}
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Build
20+
run: swift build
21+
22+
- name: Check for test targets
23+
id: check-tests
24+
run: |
25+
if swift package describe --type json | jq -e '.targets[] | select(.type == "test")' > /dev/null 2>&1; then
26+
echo "has_tests=true" >> $GITHUB_OUTPUT
27+
echo "✓ Test targets found"
28+
else
29+
echo "has_tests=false" >> $GITHUB_OUTPUT
30+
echo "→ No test targets found, skipping tests"
31+
fi
32+
33+
- name: Run tests
34+
if: steps.check-tests.outputs.has_tests == 'true'
35+
run: swift test
36+
37+
- name: Test plugin invocation
38+
run: swift package plugin subtree --help
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Generated by swift-plugin-generator 0.1.0
2+
name: Check GitHub Release
3+
4+
permissions:
5+
contents: write
6+
7+
on:
8+
schedule:
9+
- cron: '20 8,20 * * *'
10+
workflow_dispatch:
11+
12+
jobs:
13+
check:
14+
name: Check GitHub Release
15+
runs-on: ubuntu-latest
16+
env:
17+
BINARY_NAME: subtree
18+
BINARY_REPO: 21-DOT-DEV/subtree
19+
PLUGIN_REPO: 21-DOT-DEV/swift-plugin-subtree
20+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21+
SEMANTIC_VERSIONING_REGEX: "^[v]?[0-9]+\\.[0-9]+\\.[0-9]+$"
22+
outputs:
23+
BINARY_NAME: ${{ env.BINARY_NAME }}
24+
BINARY_REPO: ${{ env.BINARY_REPO }}
25+
BINARY_VERSION: ${{ steps.two.outputs.BINARY_VERSION }}
26+
PLUGIN_VERSION: ${{ steps.one.outputs.PLUGIN_VERSION }}
27+
steps:
28+
- name: Get current plugin version
29+
id: one
30+
run: |
31+
PLUGIN_RELEASE=$(gh release list -R ${{ github.repository }} -L 10 --json tagName,publishedAt \
32+
--jq 'map(select(.tagName | test(env.SEMANTIC_VERSIONING_REGEX))) | sort_by(.publishedAt) | reverse | .[0].tagName')
33+
echo "PLUGIN_VERSION=$PLUGIN_RELEASE" >> $GITHUB_OUTPUT
34+
echo "→ Plugin version: $PLUGIN_RELEASE"
35+
36+
- name: Get current binary version
37+
id: two
38+
run: |
39+
BINARY_RELEASE=$(gh release list -R ${{ env.BINARY_REPO }} -L 10 --json tagName,publishedAt \
40+
--jq 'map(select(.tagName | test(env.SEMANTIC_VERSIONING_REGEX))) | sort_by(.publishedAt) | reverse | .[0].tagName')
41+
echo "BINARY_VERSION=$BINARY_RELEASE" >> $GITHUB_OUTPUT
42+
echo "→ Binary version: $BINARY_RELEASE"
43+
44+
45+
46+
47+
update:
48+
name: Update Consumer Package
49+
needs: check
50+
runs-on: macos-15
51+
if: ${{ needs.check.outputs.PLUGIN_VERSION != needs.check.outputs.BINARY_VERSION }}
52+
env:
53+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
steps:
55+
- uses: actions/checkout@v4
56+
57+
- name: Update Package.swift with new artifact bundle
58+
run: |
59+
NEW_VERSION="${{ needs.check.outputs.BINARY_VERSION }}"
60+
NEW_URL="https://github.com/21-DOT-DEV/subtree/releases/download/${NEW_VERSION}/subtree.artifactbundle.zip"
61+
62+
echo "→ Downloading artifact bundle from: $NEW_URL"
63+
curl -sL "$NEW_URL" -o bundle.zip
64+
65+
echo "→ Calculating checksum..."
66+
NEW_CHECKSUM=$(swift package compute-checksum bundle.zip)
67+
rm bundle.zip
68+
69+
echo "→ Updating Package.swift..."
70+
# Use awk to only update the binaryTarget url and checksum
71+
awk -v url="$NEW_URL" -v checksum="$NEW_CHECKSUM" '
72+
/\.binaryTarget\(/ { in_binary = 1 }
73+
in_binary && /url:/ {
74+
sub(/url: ".*"/, "url: \"" url "\"")
75+
}
76+
in_binary && /checksum:/ {
77+
sub(/checksum: ".*"/, "checksum: \"" checksum "\"")
78+
}
79+
/\),/ && in_binary { in_binary = 0 }
80+
{ print }
81+
' Package.swift > Package.swift.tmp && mv Package.swift.tmp Package.swift
82+
83+
echo "✓ Package.swift updated"
84+
85+
- name: Validate updated Package.swift
86+
run: |
87+
swift build
88+
echo "✓ Build validation passed"
89+
90+
- name: Fetch upstream release notes
91+
run: |
92+
NEW_VERSION="${{ needs.check.outputs.BINARY_VERSION }}"
93+
NOTES=$(gh release view "$NEW_VERSION" \
94+
--repo "21-DOT-DEV/subtree" \
95+
--json body \
96+
--jq '.body' 2>/dev/null || echo "Release notes unavailable")
97+
echo "$NOTES" > release_notes.md
98+
99+
- name: Commit, push, and create release
100+
run: |
101+
NEW_VERSION="${{ needs.check.outputs.BINARY_VERSION }}"
102+
103+
git config user.name "github-actions[bot]"
104+
git config user.email "github-actions[bot]@users.noreply.github.com"
105+
git add Package.swift
106+
git commit -m "chore: update to subtree $NEW_VERSION"
107+
git push
108+
109+
gh release create "$NEW_VERSION" \
110+
--repo "21-DOT-DEV/swift-plugin-subtree" \
111+
--title "subtree $NEW_VERSION" \
112+
--notes-file release_notes.md
113+
114+
echo "✓ Committed, pushed, and created release: $NEW_VERSION"
115+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Generated by swift-plugin-generator 0.1.0
2+
name: Initial Setup (Consumer Mode)
3+
4+
on:
5+
push:
6+
branches: [main]
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
initial-setup:
13+
runs-on: ubuntu-latest
14+
env:
15+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Check if initial setup already completed
22+
id: check-setup
23+
run: |
24+
# Check if any releases exist (0 releases = first run, has releases = migration scenario)
25+
RELEASE_COUNT=$(gh release list --limit 1 --json tagName --jq 'length')
26+
if [ "$RELEASE_COUNT" -gt 0 ]; then
27+
echo "completed=true" >> $GITHUB_OUTPUT
28+
echo "✓ Initial setup already completed (releases exist)"
29+
else
30+
echo "completed=false" >> $GITHUB_OUTPUT
31+
echo "→ First run detected (no releases exist)"
32+
fi
33+
34+
- name: Extract version from upstream
35+
if: steps.check-setup.outputs.completed == 'false'
36+
id: version
37+
run: |
38+
# Extract version from artifact bundle URL: https://github.com/21-DOT-DEV/subtree/releases/download/0.0.2/subtree.artifactbundle.zip
39+
VERSION=$(echo "https://github.com/21-DOT-DEV/subtree/releases/download/0.0.2/subtree.artifactbundle.zip" | grep -oE '(v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?)' | head -1)
40+
if [ -z "$VERSION" ]; then
41+
echo "❌ Failed to extract version from URL"
42+
exit 1
43+
fi
44+
echo "version=$VERSION" >> $GITHUB_OUTPUT
45+
echo "→ Detected version: $VERSION"
46+
47+
- name: Fetch release notes from upstream
48+
if: steps.check-setup.outputs.completed == 'false'
49+
id: release-notes
50+
run: |
51+
# Fetch release notes from upstream repository
52+
VERSION="${{ steps.version.outputs.version }}"
53+
NOTES=$(gh release view "$VERSION" \
54+
--repo "21-DOT-DEV/subtree" \
55+
--json body \
56+
--jq '.body' 2>/dev/null || echo "Release notes unavailable")
57+
58+
# Save to file for later use
59+
echo "$NOTES" > release_notes.md
60+
echo "→ Fetched release notes from 21-DOT-DEV/subtree"
61+
62+
- name: Delete this workflow file
63+
if: steps.check-setup.outputs.completed == 'false'
64+
run: |
65+
git config user.name "github-actions[bot]"
66+
git config user.email "github-actions[bot]@users.noreply.github.com"
67+
68+
# Remove this workflow file
69+
git rm .github/workflows/initial-setup-consumer.yml
70+
echo "→ Removed initial-setup-consumer.yml workflow"
71+
72+
- name: Commit and push workflow deletion
73+
if: steps.check-setup.outputs.completed == 'false'
74+
run: |
75+
git commit -m "chore: remove initial setup workflow"
76+
git push
77+
echo "✓ Committed and pushed changes"
78+
79+
- name: Create initial GitHub Release
80+
if: steps.check-setup.outputs.completed == 'false'
81+
run: |
82+
VERSION="${{ steps.version.outputs.version }}"
83+
84+
# Get the current commit SHA (after our changes)
85+
COMMIT_SHA=$(git rev-parse HEAD)
86+
87+
# Create release with upstream notes
88+
gh release create "$VERSION" \
89+
--repo "21-DOT-DEV/swift-plugin-subtree" \
90+
--target "$COMMIT_SHA" \
91+
--title "subtree $VERSION" \
92+
--notes-file release_notes.md
93+
94+
echo "✓ Created initial release: $VERSION"
95+
echo "→ Plugin repository is now ready for use!"
96+
97+
- name: Setup already completed
98+
if: steps.check-setup.outputs.completed == 'true'
99+
run: |
100+
echo "ℹ️ Initial setup was already completed previously"
101+
echo "→ This is a migration scenario (releases already exist)"
102+
echo "→ Skipping initial setup steps"

.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Swift Package Manager
2+
.build/
3+
.swiftpm/
4+
Package.resolved
5+
*.xcodeproj
6+
*.xcworkspace
7+
8+
# Xcode
9+
xcuserdata/
10+
DerivedData/
11+
12+
# Build artifacts
13+
.DS_Store
14+
15+
16+
17+
18+
# Consumer mode: Resources/ can be ignored (using remote bundle)
19+
Resources/
20+

Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by swift-plugin-generator 0.1.0
2+
# Dockerfile enables Linux validation of generated plugin via `docker build .`
3+
4+
FROM swift:latest
5+
6+
WORKDIR /package
7+
8+
COPY . .
9+
10+
RUN swift build
11+
RUN swift test
12+
RUN swift package plugin subtree --help
13+
14+
CMD ["swift", "run", "subtree", "--version"]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 21-DOT-DEV
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// swift-tools-version: 6.0
2+
// Generated by swift-plugin-generator 0.1.0
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "swift-plugin-subtree",
8+
platforms: [.macOS(.v13)],
9+
products: [
10+
.executable(name: "subtree", targets: ["SubtreeExecutable"]),
11+
.plugin(name: "SubtreePlugin", targets: ["SubtreePlugin"])
12+
],
13+
dependencies: [
14+
.package(url: "https://github.com/21-DOT-DEV/swift-artifact-parser", exact: "0.0.1")
15+
],
16+
targets: [
17+
// Executable target - wraps binary using ArtifactParser
18+
.executableTarget(
19+
name: "SubtreeExecutable",
20+
dependencies: [
21+
.product(name: "ArtifactParser", package: "swift-artifact-parser")
22+
]
23+
),
24+
25+
// Plugin target - command plugin
26+
.plugin(
27+
name: "SubtreePlugin",
28+
capability: .command(
29+
intent: .custom(verb: "subtree", description: ""),
30+
permissions: [.writeToPackageDirectory(reason: "subtree may write output files")]
31+
),
32+
dependencies: ["subtree"]
33+
),
34+
35+
// Binary target - references artifact bundle
36+
37+
.binaryTarget(
38+
name: "subtree",
39+
url: "https://github.com/21-DOT-DEV/subtree/releases/download/0.0.2/subtree.artifactbundle.zip",
40+
checksum: "ce870c61f8ae9f0deafc1e9d776954c90797acedebca24a90610b85e15242a50"
41+
),
42+
43+
44+
]
45+
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Generated by swift-plugin-generator 0.1.0
2+
import Foundation
3+
import PackagePlugin
4+
5+
@main
6+
struct SubtreePlugin: CommandPlugin {
7+
func performCommand(context: PluginContext, arguments: [String]) async throws {
8+
let tool = try context.tool(named: "subtree")
9+
let toolURL = tool.url
10+
11+
let process = Process()
12+
process.executableURL = toolURL
13+
process.arguments = arguments
14+
15+
try process.run()
16+
process.waitUntilExit()
17+
18+
if process.terminationStatus != 0 {
19+
Diagnostics.error("subtree exited with code \(process.terminationStatus)")
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)