Skip to content

Commit 2a14a27

Browse files
pepicrftclaude
andcommitted
feat: initial release of magnolia CLI
Magnolia is a Rust CLI tool that enables developers to run GitLab CI, GitHub Actions, and Forgejo/Gitea Actions pipelines locally. Features: - Auto-detect CI systems in repositories - List available jobs and workflows - Run jobs locally (simulation mode) - Support for GitLab CI, GitHub Actions, and Forgejo Actions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
0 parents  commit 2a14a27

16 files changed

Lines changed: 1922 additions & 0 deletions

File tree

.forgejo/workflows/test.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
container: rust:latest
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Build project
18+
run: |
19+
cargo build --release
20+
echo "Build completed successfully"
21+
22+
- name: Upload artifact
23+
uses: actions/upload-artifact@v4
24+
with:
25+
name: magnolia
26+
path: target/release/magnolia
27+
28+
test:
29+
runs-on: ubuntu-latest
30+
container: rust:latest
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v4
34+
35+
- name: Run tests
36+
run: |
37+
cargo test --all
38+
echo "Tests passed"
39+
40+
lint:
41+
runs-on: ubuntu-latest
42+
container: rust:latest
43+
steps:
44+
- name: Checkout code
45+
uses: actions/checkout@v4
46+
47+
- name: Check formatting
48+
run: |
49+
rustup component add rustfmt
50+
cargo fmt -- --check
51+
52+
- name: Run clippy
53+
run: |
54+
rustup component add clippy
55+
cargo clippy -- -D warnings

.github/workflows/release.yml

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
check-release:
19+
name: Check if release is needed
20+
runs-on: ubuntu-latest
21+
outputs:
22+
should_release: ${{ steps.check.outputs.should_release }}
23+
new_version: ${{ steps.check.outputs.new_version }}
24+
steps:
25+
- uses: actions/checkout@v4
26+
with:
27+
fetch-depth: 0
28+
29+
- name: Install git-cliff
30+
uses: taiki-e/install-action@v2
31+
with:
32+
tool: git-cliff
33+
34+
- name: Check if release is needed
35+
id: check
36+
run: |
37+
# Get the latest tag or use v0.0.0 if no tags exist
38+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
39+
echo "Latest tag: $LATEST_TAG"
40+
41+
# Check if there are any releasable commits since the last tag
42+
if git-cliff --unreleased --strip all | grep -q '###'; then
43+
# Calculate the next version
44+
NEW_VERSION=$(git-cliff --bumped-version --strip all)
45+
echo "New version: $NEW_VERSION"
46+
echo "should_release=true" >> $GITHUB_OUTPUT
47+
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
48+
else
49+
echo "No releasable commits found"
50+
echo "should_release=false" >> $GITHUB_OUTPUT
51+
fi
52+
53+
build:
54+
name: Build ${{ matrix.target }}
55+
needs: check-release
56+
if: needs.check-release.outputs.should_release == 'true'
57+
runs-on: ${{ matrix.os }}
58+
strategy:
59+
fail-fast: false
60+
matrix:
61+
include:
62+
# Linux x86_64
63+
- target: x86_64-unknown-linux-gnu
64+
os: ubuntu-latest
65+
archive: tar.gz tar.xz tar.zst
66+
- target: x86_64-unknown-linux-musl
67+
os: ubuntu-latest
68+
archive: tar.gz tar.xz tar.zst
69+
70+
# Linux ARM64
71+
- target: aarch64-unknown-linux-gnu
72+
os: ubuntu-latest
73+
archive: tar.gz tar.xz tar.zst
74+
- target: aarch64-unknown-linux-musl
75+
os: ubuntu-latest
76+
archive: tar.gz tar.xz tar.zst
77+
78+
# Linux ARMv7
79+
- target: armv7-unknown-linux-gnueabihf
80+
os: ubuntu-latest
81+
archive: tar.gz tar.xz tar.zst
82+
- target: armv7-unknown-linux-musleabihf
83+
os: ubuntu-latest
84+
archive: tar.gz tar.xz tar.zst
85+
86+
# macOS x86_64
87+
- target: x86_64-apple-darwin
88+
os: macos-latest
89+
archive: tar.gz tar.xz tar.zst
90+
91+
# macOS ARM64
92+
- target: aarch64-apple-darwin
93+
os: macos-latest
94+
archive: tar.gz tar.xz tar.zst
95+
96+
# Windows x86_64
97+
- target: x86_64-pc-windows-msvc
98+
os: windows-latest
99+
archive: zip
100+
101+
# Windows ARM64
102+
- target: aarch64-pc-windows-msvc
103+
os: windows-latest
104+
archive: zip
105+
106+
steps:
107+
- uses: actions/checkout@v4
108+
109+
- name: Update Cargo.toml version
110+
shell: bash
111+
run: |
112+
VERSION="${{ needs.check-release.outputs.new_version }}"
113+
# Remove 'v' prefix if present
114+
VERSION="${VERSION#v}"
115+
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" Cargo.toml
116+
rm Cargo.toml.bak 2>/dev/null || true
117+
echo "Updated Cargo.toml to version $VERSION"
118+
grep "^version = " Cargo.toml
119+
120+
- name: Install Rust
121+
uses: dtolnay/rust-toolchain@stable
122+
with:
123+
targets: ${{ matrix.target }}
124+
125+
- name: Install cross-compilation tools (Linux)
126+
if: runner.os == 'Linux'
127+
run: |
128+
cargo install cross --git https://github.com/cross-rs/cross
129+
130+
- name: Build binary (Linux cross-compile)
131+
if: runner.os == 'Linux'
132+
run: |
133+
cross build --release --target ${{ matrix.target }}
134+
135+
- name: Build binary (macOS/Windows)
136+
if: runner.os != 'Linux'
137+
run: |
138+
cargo build --release --target ${{ matrix.target }}
139+
140+
- name: Create archives
141+
shell: bash
142+
run: |
143+
VERSION="${{ needs.check-release.outputs.new_version }}"
144+
TARGET="${{ matrix.target }}"
145+
ARCHIVE_FORMATS="${{ matrix.archive }}"
146+
147+
cd target/$TARGET/release
148+
149+
# Determine binary name based on platform
150+
if [[ "$TARGET" == *"windows"* ]]; then
151+
BINARY="magnolia.exe"
152+
else
153+
BINARY="magnolia"
154+
fi
155+
156+
# Create a staging directory
157+
mkdir -p ../../../dist
158+
DIST_DIR="$(pwd)/../../../dist"
159+
160+
# Create temporary directory for archive contents
161+
TEMP_DIR=$(mktemp -d)
162+
163+
# Copy binary
164+
cp "$BINARY" "$TEMP_DIR/"
165+
166+
# Create README for the archive
167+
cat > "$TEMP_DIR/README.txt" << 'EOF'
168+
Magnolia - Run GitLab, GitHub, and Forgejo pipelines locally
169+
170+
Usage:
171+
magnolia detect # Detect CI systems in current directory
172+
magnolia list # List available jobs
173+
magnolia run <job> # Run a specific job
174+
175+
For more information, visit: https://github.com/tuist/magnolia
176+
EOF
177+
178+
# Create archives based on format
179+
cd "$TEMP_DIR"
180+
for FORMAT in $ARCHIVE_FORMATS; do
181+
ARCHIVE_NAME="magnolia-${VERSION}-${TARGET}"
182+
183+
case $FORMAT in
184+
tar.gz)
185+
tar czf "${DIST_DIR}/${ARCHIVE_NAME}.tar.gz" *
186+
;;
187+
tar.xz)
188+
tar cJf "${DIST_DIR}/${ARCHIVE_NAME}.tar.xz" *
189+
;;
190+
tar.zst)
191+
tar --zstd -cf "${DIST_DIR}/${ARCHIVE_NAME}.tar.zst" *
192+
;;
193+
zip)
194+
7z a "${DIST_DIR}/${ARCHIVE_NAME}.zip" *
195+
;;
196+
esac
197+
done
198+
199+
# Cleanup
200+
rm -rf "$TEMP_DIR"
201+
202+
- name: Generate checksums
203+
shell: bash
204+
run: |
205+
cd dist
206+
for file in magnolia-*; do
207+
if [[ "$RUNNER_OS" == "Windows" ]]; then
208+
certutil -hashfile "$file" SHA256 | grep -v "hash" | tr -d ' \r\n' > "${file}.sha256"
209+
else
210+
shasum -a 256 "$file" | cut -d ' ' -f 1 > "${file}.sha256"
211+
fi
212+
done
213+
214+
- name: Upload artifacts
215+
uses: actions/upload-artifact@v4
216+
with:
217+
name: magnolia-${{ matrix.target }}
218+
path: dist/*
219+
retention-days: 1
220+
221+
create-release:
222+
name: Create GitHub Release
223+
needs: [check-release, build]
224+
runs-on: ubuntu-latest
225+
steps:
226+
- uses: actions/checkout@v4
227+
with:
228+
fetch-depth: 0
229+
230+
- name: Install git-cliff
231+
uses: taiki-e/install-action@v2
232+
with:
233+
tool: git-cliff
234+
235+
- name: Download all artifacts
236+
uses: actions/download-artifact@v4
237+
with:
238+
path: artifacts
239+
pattern: magnolia-*
240+
merge-multiple: true
241+
242+
- name: Update Cargo.toml version
243+
run: |
244+
VERSION="${{ needs.check-release.outputs.new_version }}"
245+
# Remove 'v' prefix if present
246+
VERSION="${VERSION#v}"
247+
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" Cargo.toml
248+
rm Cargo.toml.bak 2>/dev/null || true
249+
echo "Updated Cargo.toml to version $VERSION"
250+
grep "^version = " Cargo.toml
251+
252+
- name: Generate changelog
253+
run: |
254+
VERSION="${{ needs.check-release.outputs.new_version }}"
255+
git-cliff --tag "$VERSION" -o CHANGELOG.md
256+
257+
# Generate release notes (latest version only, without version header)
258+
git-cliff --tag "$VERSION" --unreleased --strip all -o RELEASE_NOTES.md
259+
260+
# Remove the version header line (e.g., "## [0.1.0] - 2025-10-24")
261+
# Keep only the grouped changes
262+
tail -n +2 RELEASE_NOTES.md > RELEASE_NOTES.tmp && mv RELEASE_NOTES.tmp RELEASE_NOTES.md
263+
264+
- name: Create SHA256SUMS file
265+
run: |
266+
cd artifacts
267+
cat *.sha256 > SHA256SUMS
268+
# Also create individual checksums in a readable format
269+
for file in magnolia-*; do
270+
if [[ ! "$file" == *.sha256 ]]; then
271+
sha256sum "$file" >> SHA256SUMS.txt
272+
fi
273+
done
274+
275+
- name: Create GitHub Release
276+
uses: softprops/action-gh-release@v2
277+
with:
278+
tag_name: ${{ needs.check-release.outputs.new_version }}
279+
name: ${{ needs.check-release.outputs.new_version }}
280+
body_path: RELEASE_NOTES.md
281+
draft: false
282+
prerelease: false
283+
files: |
284+
artifacts/magnolia-*
285+
artifacts/SHA256SUMS
286+
artifacts/SHA256SUMS.txt
287+
env:
288+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
289+
290+
- name: Commit version bumps
291+
run: |
292+
git config user.name "github-actions[bot]"
293+
git config user.email "github-actions[bot]@users.noreply.github.com"
294+
git add Cargo.toml CHANGELOG.md
295+
git commit -m "chore(release): bump version to ${{ needs.check-release.outputs.new_version }}"
296+
git push
297+
env:
298+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

0 commit comments

Comments
 (0)