Skip to content

Commit 225331d

Browse files
LegnaOSclaude
andcommitted
feat: add GitHub Actions release workflow + cross-platform Rust native builds
- 4-stage CI: prepare → native (Rust) → compile (Bun) → publish (npm) - Rust addons built on native runners: macos-14 (arm64), macos-13 (x64), ubuntu (x64), ubuntu-arm (arm64) - compile.ts now supports --target flag for cross-compilation - Fix OML agent field type mismatch (object → string) - Trigger: push v* tag or manual workflow_dispatch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c6cf0ea commit 225331d

3 files changed

Lines changed: 265 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: 'Version to publish (e.g. 1.8.3)'
11+
required: true
12+
skip_win32:
13+
description: 'Skip win32 build'
14+
type: boolean
15+
default: true
16+
dry_run:
17+
description: 'Dry run (no actual publish)'
18+
type: boolean
19+
default: false
20+
21+
permissions:
22+
contents: write
23+
24+
env:
25+
BUN_VERSION: '1.2.12'
26+
RUST_TOOLCHAIN: 'stable'
27+
28+
jobs:
29+
# ── Step 1: Bump version & build webui ──
30+
prepare:
31+
runs-on: ubuntu-latest
32+
outputs:
33+
version: ${{ steps.version.outputs.version }}
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- uses: oven-sh/setup-bun@v2
38+
with:
39+
bun-version: ${{ env.BUN_VERSION }}
40+
41+
- name: Determine version
42+
id: version
43+
run: |
44+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
45+
echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
46+
else
47+
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
48+
fi
49+
50+
- name: Bump version
51+
run: bun run scripts/bump.ts ${{ steps.version.outputs.version }}
52+
53+
- name: Build WebUI
54+
run: bun run scripts/build-webui.ts
55+
56+
- name: Upload prepared source
57+
uses: actions/upload-artifact@v4
58+
with:
59+
name: prepared-source
60+
path: |
61+
.
62+
!node_modules
63+
!.git
64+
!native/target
65+
retention-days: 1
66+
67+
# ── Step 2: Build Rust native addons (per-platform runners) ──
68+
native:
69+
needs: prepare
70+
strategy:
71+
fail-fast: false
72+
matrix:
73+
include:
74+
- runner: macos-14
75+
platform: darwin
76+
arch: arm64
77+
rust_target: aarch64-apple-darwin
78+
- runner: macos-13
79+
platform: darwin
80+
arch: x64
81+
rust_target: x86_64-apple-darwin
82+
- runner: ubuntu-latest
83+
platform: linux
84+
arch: x64
85+
rust_target: x86_64-unknown-linux-gnu
86+
- runner: ubuntu-24.04-arm
87+
platform: linux
88+
arch: arm64
89+
rust_target: aarch64-unknown-linux-gnu
90+
runs-on: ${{ matrix.runner }}
91+
steps:
92+
- uses: actions/download-artifact@v4
93+
with:
94+
name: prepared-source
95+
96+
- uses: dtolnay/rust-toolchain@stable
97+
with:
98+
targets: ${{ matrix.rust_target }}
99+
100+
- name: Build native addons
101+
working-directory: native
102+
run: |
103+
CRATES="sandbox file-search apply-patch"
104+
for crate in $CRATES; do
105+
echo "Building $crate for ${{ matrix.rust_target }}..."
106+
cargo build --release -p "legnacode-${crate}" --target ${{ matrix.rust_target }}
107+
108+
# Find the compiled library
109+
CRATE_UNDER=$(echo "$crate" | tr '-' '_')
110+
LIB_NAME="legnacode_${CRATE_UNDER}"
111+
if [ "${{ matrix.platform }}" = "darwin" ]; then
112+
SRC="target/${{ matrix.rust_target }}/release/lib${LIB_NAME}.dylib"
113+
elif [ "${{ matrix.platform }}" = "linux" ]; then
114+
SRC="target/${{ matrix.rust_target }}/release/lib${LIB_NAME}.so"
115+
fi
116+
117+
DEST="../src/native/${crate}.${{ matrix.platform }}-${{ matrix.arch }}.node"
118+
cp "$SRC" "$DEST"
119+
echo " → $DEST"
120+
done
121+
122+
- name: Upload native addons
123+
uses: actions/upload-artifact@v4
124+
with:
125+
name: native-${{ matrix.platform }}-${{ matrix.arch }}
126+
path: src/native/*.node
127+
retention-days: 1
128+
129+
# ── Step 3: Cross-compile Bun binaries (all platforms) ──
130+
compile:
131+
needs: [prepare, native]
132+
runs-on: ubuntu-latest
133+
strategy:
134+
fail-fast: false
135+
matrix:
136+
include:
137+
- target: bun-darwin-arm64
138+
pkg: legnacode-darwin-arm64
139+
os: darwin
140+
arch: arm64
141+
- target: bun-darwin-x64
142+
pkg: legnacode-darwin-x64
143+
os: darwin
144+
arch: x64
145+
- target: bun-darwin-x64-baseline
146+
pkg: legnacode-darwin-x64-baseline
147+
os: darwin
148+
arch: x64
149+
- target: bun-linux-x64
150+
pkg: legnacode-linux-x64
151+
os: linux
152+
arch: x64
153+
- target: bun-linux-x64-baseline
154+
pkg: legnacode-linux-x64-baseline
155+
os: linux
156+
arch: x64
157+
- target: bun-linux-arm64
158+
pkg: legnacode-linux-arm64
159+
os: linux
160+
arch: arm64
161+
- target: bun-windows-x64
162+
pkg: legnacode-win32-x64
163+
os: win32
164+
arch: x64
165+
steps:
166+
- name: Skip win32 check
167+
id: skip
168+
run: |
169+
if [ "${{ matrix.os }}" = "win32" ] && [ "${{ inputs.skip_win32 }}" != "false" ]; then
170+
echo "skip=true" >> "$GITHUB_OUTPUT"
171+
else
172+
echo "skip=false" >> "$GITHUB_OUTPUT"
173+
fi
174+
175+
- uses: actions/download-artifact@v4
176+
if: steps.skip.outputs.skip == 'false'
177+
with:
178+
name: prepared-source
179+
180+
# Download matching native addons into src/native/
181+
- uses: actions/download-artifact@v4
182+
if: steps.skip.outputs.skip == 'false' && matrix.os != 'win32'
183+
with:
184+
name: native-${{ matrix.os }}-${{ matrix.arch }}
185+
path: src/native/
186+
187+
- uses: oven-sh/setup-bun@v2
188+
if: steps.skip.outputs.skip == 'false'
189+
with:
190+
bun-version: ${{ env.BUN_VERSION }}
191+
192+
- name: Compile for ${{ matrix.target }}
193+
if: steps.skip.outputs.skip == 'false'
194+
run: |
195+
bun run scripts/compile.ts --target=${{ matrix.target }}
196+
mkdir -p dist/${{ matrix.pkg }}/bin
197+
EXT=""
198+
if [ "${{ matrix.os }}" = "win32" ]; then EXT=".exe"; fi
199+
mv legna${EXT} dist/${{ matrix.pkg }}/bin/legna${EXT}
200+
201+
# Copy native .node files into the package bin/ dir
202+
if [ "${{ matrix.os }}" != "win32" ]; then
203+
for f in src/native/*.${{ matrix.os }}-${{ matrix.arch }}.node; do
204+
[ -f "$f" ] && cp "$f" dist/${{ matrix.pkg }}/bin/
205+
done
206+
fi
207+
208+
# Copy package.json
209+
cp .npm-packages/${{ matrix.pkg }}/package.json dist/${{ matrix.pkg }}/package.json
210+
211+
- name: Upload compiled package
212+
if: steps.skip.outputs.skip == 'false'
213+
uses: actions/upload-artifact@v4
214+
with:
215+
name: ${{ matrix.pkg }}
216+
path: dist/${{ matrix.pkg }}
217+
retention-days: 1
218+
219+
# ── Step 4: Publish to npm ──
220+
publish:
221+
needs: [prepare, compile]
222+
runs-on: ubuntu-latest
223+
steps:
224+
- uses: actions/download-artifact@v4
225+
with:
226+
path: packages
227+
pattern: legnacode-*
228+
merge-multiple: false
229+
230+
- uses: actions/setup-node@v4
231+
with:
232+
node-version: '20'
233+
registry-url: 'https://registry.npmjs.org'
234+
235+
- name: Publish platform packages
236+
env:
237+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
238+
run: |
239+
DRY_RUN=""
240+
if [ "${{ inputs.dry_run }}" = "true" ]; then DRY_RUN="--dry-run"; fi
241+
242+
for pkg_dir in packages/legnacode-*/; do
243+
if [ ! -f "$pkg_dir/package.json" ]; then continue; fi
244+
pkg_name=$(node -p "require('./${pkg_dir}/package.json').name")
245+
echo "Publishing ${pkg_name}@${{ needs.prepare.outputs.version }}..."
246+
cd "$pkg_dir"
247+
npm publish --access public $DRY_RUN || echo "⚠️ Failed or already published: $pkg_name"
248+
cd -
249+
done
250+
251+
- name: Summary
252+
run: |
253+
echo "## Published v${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY
254+
echo "" >> $GITHUB_STEP_SUMMARY
255+
for pkg_dir in packages/legnacode-*/; do
256+
if [ ! -f "$pkg_dir/package.json" ]; then continue; fi
257+
pkg_name=$(node -p "require('./${pkg_dir}/package.json').name")
258+
echo "- ${pkg_name}" >> $GITHUB_STEP_SUMMARY
259+
done

scripts/compile.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ const { defines, features } = parseBunfig()
4444
// Override build-time stamp
4545
defines['MACRO.BUILD_TIME'] = `'"${new Date().toISOString()}"'`
4646

47+
// Parse --target flag for cross-compilation (e.g. --target=bun-darwin-arm64)
48+
const targetArg = process.argv.find(a => a.startsWith('--target='))
49+
const target = targetArg ? targetArg.split('=')[1] : undefined
50+
4751
const result = await Bun.build({
4852
entrypoints: [resolve(ROOT, 'src/entrypoints/cli.tsx')],
4953
outdir: resolve(ROOT, '.compile-tmp'),
50-
target: 'bun',
54+
target: target ?? 'bun',
5155
compile: true,
5256
define: defines,
5357
features,

src/plugins/bundled/oml/agents.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,7 @@ export function getAgentSkills(): BundledSkillDefinition[] {
4747
argumentHint: '<task description>',
4848
userInvocable: true,
4949
model: agent.model,
50-
agent: {
51-
type: agent.name,
52-
model: agent.model,
53-
},
50+
agent: agent.name,
5451
getPromptForCommand: (args: string) => {
5552
const disallowed = agent.disallowedTools?.length
5653
? `\n\nTOOL RESTRICTIONS: You MUST NOT use these tools: ${agent.disallowedTools.join(', ')}.`

0 commit comments

Comments
 (0)