History

install CLI to PATH on launch, fix desktop CI to download pre-built CLI

Rhys Sullivan committed 7 months ago

Commit d55c42f1 parent 41f5dc24 files · +116 −55Browse files

Unified · Split

4 files
  1. .github/workflows/publish-desktop.yml +44 −52
  2. .github/workflows/publish-executor-package.yml +7 −0
  3. .github/workflows/release.yml +5 −2
  4. apps/desktop/src/main.ts +60 −1
.github/workflows/publish-desktop.yml+44−52
@@ -2,13 +2,10 @@ name: Publish Desktop App · 1 unmodified line
22 run-name: "${{ format('publish desktop {0}', github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name) }}"
33
44 on:
5− push:
6− tags:
7− - "v*"
85 workflow_dispatch:
96 inputs:
107 tag:
11− description: Git tag to publish
8+ description: Git tag to publish (e.g. v1.4.1)
129 required: true
1310 type: string
1411
@@ -28,15 +25,19 @@ jobs: · 13 unmodified lines
2825 - os: macos-latest
2926 arch: arm64
3027 platform: mac
28+ cli-asset: executor-darwin-arm64.zip
3129 - os: macos-latest
3230 arch: x64
3331 platform: mac
32+ cli-asset: executor-darwin-x64.zip
3433 - os: ubuntu-latest
3534 arch: x64
3635 platform: linux
36+ cli-asset: executor-linux-x64.tar.gz
3737 - os: windows-latest
3838 arch: x64
3939 platform: win
40+ cli-asset: executor-windows-x64.zip
4041
4142 runs-on: ${{ matrix.os }}
4243
@@ -44,7 +45,7 @@ jobs: · 1 unmodified line
4445 - name: Checkout
4546 uses: actions/checkout@v4
4647 with:
47− ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
48+ ref: refs/tags/${{ inputs.tag }}
4849
4950 - name: Setup Bun
5051 uses: oven-sh/setup-bun@v2
@@ -63,54 +64,60 @@ jobs: · 12 unmodified lines
6364 run: bun run build
6465 working-directory: apps/web
6566
66− - name: Build CLI sidecar binary
67− run: bun run src/build.ts binary --single
68− working-directory: apps/cli
69−
70− - name: Copy sidecar to desktop resources
71− shell: bash
67+ - name: Download CLI binary from release
68+ env:
69+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7270 run: |
7371 mkdir -p apps/desktop/resources
74− if [ "${{ matrix.platform }}" = "win" ]; then
75− BINARY_NAME="executor.exe"
72+ gh release download "${{ inputs.tag }}" \
73+ --repo "${{ github.repository }}" \
74+ --pattern "${{ matrix.cli-asset }}" \
75+ --dir /tmp/cli-download
76+
77+ - name: Extract CLI sidecar (unix)
78+ if: matrix.platform != 'win'
79+ run: |
80+ cd /tmp/cli-download
81+ if [[ "${{ matrix.cli-asset }}" == *.tar.gz ]]; then
82+ tar -xzf "${{ matrix.cli-asset }}"
7683 else
77− BINARY_NAME="executor"
84+ unzip -o "${{ matrix.cli-asset }}"
7885 fi
79−
80− PLATFORM="${{ matrix.platform == 'mac' && 'darwin' || matrix.platform == 'win' && 'windows' || 'linux' }}"
81− ARCH="${{ matrix.arch }}"
82− SRC="apps/cli/dist/executor-${PLATFORM}-${ARCH}/bin"
83−
84− cp "${SRC}/${BINARY_NAME}" "apps/desktop/resources/${BINARY_NAME}"
85− chmod +x "apps/desktop/resources/${BINARY_NAME}" 2>/dev/null || true
86−
87− if [ -f "${SRC}/emscripten-module.wasm" ]; then
88− cp "${SRC}/emscripten-module.wasm" "apps/desktop/resources/"
86+ cp executor apps/desktop/resources/executor
87+ chmod +x apps/desktop/resources/executor
88+ if [ -f emscripten-module.wasm ]; then
89+ cp emscripten-module.wasm apps/desktop/resources/
8990 fi
91+ working-directory: ${{ github.workspace }}
92+
93+ - name: Extract CLI sidecar (windows)
94+ if: matrix.platform == 'win'
95+ shell: pwsh
96+ run: |
97+ Expand-Archive -Path "/tmp/cli-download/${{ matrix.cli-asset }}" -DestinationPath "/tmp/cli-extract" -Force
98+ Copy-Item "/tmp/cli-extract/executor.exe" "apps/desktop/resources/executor.exe"
99+ if (Test-Path "/tmp/cli-extract/emscripten-module.wasm") {
100+ Copy-Item "/tmp/cli-extract/emscripten-module.wasm" "apps/desktop/resources/"
101+ }
90102
91103 - name: Build desktop app (TypeScript)
92104 run: bunx tsc -p tsconfig.json
93105 working-directory: apps/desktop
94106
95107 - name: Build desktop distributables
96− env:
97− GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
98− run: |
99− npx electron-builder --${{ matrix.platform }} --${{ matrix.arch }} --dir
108+ run: npx electron-builder --${{ matrix.platform }} --${{ matrix.arch }} --dir
100109 working-directory: apps/desktop
101110
102− - name: Package artifacts (macOS)
111+ - name: Package (macOS)
103112 if: matrix.platform == 'mac'
104113 working-directory: apps/desktop/dist
105114 run: |
106− # Find the .app and zip it
107115 APP_DIR=$(find . -name "*.app" -maxdepth 2 | head -1)
108116 if [ -n "$APP_DIR" ]; then
109− ZIP_NAME="Executor-${{ matrix.arch }}-mac.zip"
110− ditto -c -k --sequesterRsrc --keepParent "$APP_DIR" "$ZIP_NAME"
117+ ditto -c -k --sequesterRsrc --keepParent "$APP_DIR" "Executor-${{ matrix.arch }}-mac.zip"
111118 fi
112119
113− - name: Package artifacts (Linux)
120+ - name: Package (Linux)
114121 if: matrix.platform == 'linux'
115122 working-directory: apps/desktop/dist
116123 run: |
@@ -119,7 +126,7 @@ jobs: · 2 unmodified lines
119126 tar -czf "Executor-x64-linux.tar.gz" -C "$UNPACKED" .
120127 fi
121128
122− - name: Package artifacts (Windows)
129+ - name: Package (Windows)
123130 if: matrix.platform == 'win'
124131 working-directory: apps/desktop/dist
125132 shell: pwsh
@@ -134,8 +141,8 @@ jobs: · 8 unmodified lines
134141 with:
135142 name: desktop-${{ matrix.platform }}-${{ matrix.arch }}
136143 path: |
137− apps/desktop/dist/*.zip
138− apps/desktop/dist/*.tar.gz
144+ apps/desktop/dist/Executor-*.zip
145+ apps/desktop/dist/Executor-*.tar.gz
139146 if-no-files-found: warn
140147
141148 release:
@@ -149,26 +156,11 @@ jobs: · 7 unmodified lines
149156 path: artifacts
150157 merge-multiple: true
151158
152− - name: List artifacts
153− run: find artifacts -type f | head -20
154−
155159 - name: Upload to GitHub Release
156160 env:
157161 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
158− TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
159162 run: |
160− # Wait for the CLI release to create the GitHub release first
161− for i in $(seq 1 30); do
162− if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
163− echo "Release $TAG found"
164− break
165− fi
166− echo "Waiting for release $TAG to be created... ($i/30)"
167− sleep 10
168− done
169−
170− # Upload desktop artifacts
171163 find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) | while read file; do
172164 echo "Uploading: $file"
173− gh release upload "$TAG" "$file" --repo "$GITHUB_REPOSITORY" --clobber || true
165+ gh release upload "${{ inputs.tag }}" "$file" --repo "${{ github.repository }}" --clobber || true
174166 done
.github/workflows/publish-executor-package.yml+7−0
@@ -13,6 +13,7 @@ on: · 12 unmodified lines
1313 type: string
1414
1515 permissions:
16+ actions: write
1617 contents: write
1718 id-token: write
1819
@@ -64,3 +65,9 @@ jobs: · 45 unmodified lines
6465 export GITHUB_REF_NAME="$RELEASE_TAG"
6566 export GITHUB_REF="refs/tags/$RELEASE_TAG"
6667 bun run release:publish
68+
69+ - name: Trigger desktop build
70+ env:
71+ GH_TOKEN: ${{ github.token }}
72+ RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
73+ run: gh workflow run publish-desktop.yml -f tag="$RELEASE_TAG"
.github/workflows/release.yml+5−2
@@ -88,11 +88,14 @@ jobs: · 87 unmodified lines
8888 git tag "$tag"
8989 git push origin "$tag"
9090
91− - name: Trigger publish workflows
91+ - name: Trigger CLI publish
9292 if: steps.changesets.outputs.hasChangesets == 'false' && steps.detect_release.outputs.changed == 'true'
9393 env:
9494 GH_TOKEN: ${{ github.token }}
9595 run: |
9696 tag="v${{ steps.detect_release.outputs.version }}"
9797 gh workflow run publish-executor-package.yml --ref "$tag" -f tag="$tag"
98− gh workflow run publish-desktop.yml --ref "$tag" -f tag="$tag"
98+
99+ # Desktop build downloads CLI binaries from the release, so it must
100+ # run after CLI publish completes. Trigger it from the CLI workflow
101+ # or manually via: gh workflow run publish-desktop.yml -f tag=vX.Y.Z
apps/desktop/src/main.ts+60−1
@@ -10,7 +10,10 @@ import { · 9 unmodified lines
1010 } from "electron";
1111 import { spawn, type ChildProcess } from "node:child_process";
1212 import { join, resolve, basename } from "node:path";
13−import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
13+import {
14+ existsSync, readFileSync, writeFileSync, mkdirSync,
15+ copyFileSync, chmodSync, appendFileSync,
16+} from "node:fs";
1417 import { homedir } from "node:os";
1518
1619 // ---------------------------------------------------------------------------
@@ -22,6 +25,59 @@ const SERVER_STARTUP_TIMEOUT_MS = 30_000; · 5 unmodified lines
2225 const SETTINGS_DIR = join(homedir(), ".executor");
2326 const SETTINGS_PATH = join(SETTINGS_DIR, "desktop-settings.json");
2427
28+const CLI_BIN_DIR = join(SETTINGS_DIR, "bin");
29+const CLI_BIN_PATH = join(CLI_BIN_DIR, process.platform === "win32" ? "executor.exe" : "executor");
30+
31+// ---------------------------------------------------------------------------
32+// CLI install — copy sidecar to ~/.executor/bin and patch shell PATH
33+// ---------------------------------------------------------------------------
34+
35+const installCli = (): void => {
36+ if (isDev) return;
37+
38+ const sidecar = join(process.resourcesPath, binaryName);
39+ if (!existsSync(sidecar)) return;
40+
41+ // Copy binary
42+ mkdirSync(CLI_BIN_DIR, { recursive: true });
43+ copyFileSync(sidecar, CLI_BIN_PATH);
44+ try { chmodSync(CLI_BIN_PATH, 0o755); } catch {}
45+
46+ // Copy WASM if present
47+ const wasm = join(process.resourcesPath, "emscripten-module.wasm");
48+ if (existsSync(wasm)) {
49+ copyFileSync(wasm, join(CLI_BIN_DIR, "emscripten-module.wasm"));
50+ }
51+
52+ // Patch shell profiles with PATH
53+ if (process.platform === "win32") return;
54+
55+ const pathLine = `export PATH="${CLI_BIN_DIR}:$PATH"`;
56+ const profiles = [
57+ join(homedir(), ".zshrc"),
58+ join(homedir(), ".bashrc"),
59+ join(homedir(), ".bash_profile"),
60+ ];
61+
62+ // Fish uses a different syntax
63+ const fishConfig = join(homedir(), ".config", "fish", "config.fish");
64+ const fishLine = `fish_add_path "${CLI_BIN_DIR}"`;
65+
66+ for (const profile of profiles) {
67+ if (!existsSync(profile)) continue;
68+ const content = readFileSync(profile, "utf-8");
69+ if (content.includes(CLI_BIN_DIR)) continue;
70+ appendFileSync(profile, `\n# Added by Executor desktop app\n${pathLine}\n`);
71+ }
72+
73+ if (existsSync(fishConfig)) {
74+ const content = readFileSync(fishConfig, "utf-8");
75+ if (!content.includes(CLI_BIN_DIR)) {
76+ appendFileSync(fishConfig, `\n# Added by Executor desktop app\n${fishLine}\n`);
77+ }
78+ }
79+};
80+
2581 // ---------------------------------------------------------------------------
2682 // Settings persistence
2783 // ---------------------------------------------------------------------------
@@ -630,6 +686,9 @@ app.whenReady().then(async () => { · 602 unmodified lines
630686 // Clear cached web content so we always load the latest UI
631687 await session.defaultSession.clearCache();
632688
689+ // Install/update CLI binary to ~/.executor/bin
690+ installCli();
691+
633692 settings = loadSettings();
634693 setupIPC();
635694 buildMenu();