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
2run-name: "${{ format('publish desktop {0}', github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name) }}"2run-name: "${{ format('publish desktop {0}', github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name) }}"
3 3
4on:4on:
5 push:
6 tags:
7 - "v*"
8 workflow_dispatch:5 workflow_dispatch:
9 inputs:6 inputs:
10 tag:7 tag:
11 description: Git tag to publish8 description: Git tag to publish (e.g. v1.4.1)
12 required: true9 required: true
13 type: string10 type: string
14 11
@@ -28,15 +25,19 @@ jobs: · 13 unmodified lines
28 - os: macos-latest25 - os: macos-latest
29 arch: arm6426 arch: arm64
30 platform: mac27 platform: mac
28 cli-asset: executor-darwin-arm64.zip
31 - os: macos-latest29 - os: macos-latest
32 arch: x6430 arch: x64
33 platform: mac31 platform: mac
32 cli-asset: executor-darwin-x64.zip
34 - os: ubuntu-latest33 - os: ubuntu-latest
35 arch: x6434 arch: x64
36 platform: linux35 platform: linux
36 cli-asset: executor-linux-x64.tar.gz
37 - os: windows-latest37 - os: windows-latest
38 arch: x6438 arch: x64
39 platform: win39 platform: win
40 cli-asset: executor-windows-x64.zip
40 41
41 runs-on: ${{ matrix.os }}42 runs-on: ${{ matrix.os }}
42 43
@@ -44,7 +45,7 @@ jobs: · 1 unmodified line
44 - name: Checkout45 - name: Checkout
45 uses: actions/checkout@v446 uses: actions/checkout@v4
46 with:47 with:
47 ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}48 ref: refs/tags/${{ inputs.tag }}
48 49
49 - name: Setup Bun50 - name: Setup Bun
50 uses: oven-sh/setup-bun@v251 uses: oven-sh/setup-bun@v2
@@ -63,54 +64,60 @@ jobs: · 12 unmodified lines
63 run: bun run build64 run: bun run build
64 working-directory: apps/web65 working-directory: apps/web
65 66
66 - name: Build CLI sidecar binary67 - name: Download CLI binary from release
67 run: bun run src/build.ts binary --single68 env:
68 working-directory: apps/cli69 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69
70 - name: Copy sidecar to desktop resources
71 shell: bash
72 run: |70 run: |
73 mkdir -p apps/desktop/resources71 mkdir -p apps/desktop/resources
74 if [ "${{ matrix.platform }}" = "win" ]; then72 gh release download "${{ inputs.tag }}" \
75 BINARY_NAME="executor.exe"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 }}"
76 else83 else
77 BINARY_NAME="executor"84 unzip -o "${{ matrix.cli-asset }}"
78 fi85 fi
79 86 cp executor apps/desktop/resources/executor
80 PLATFORM="${{ matrix.platform == 'mac' && 'darwin' || matrix.platform == 'win' && 'windows' || 'linux' }}"87 chmod +x apps/desktop/resources/executor
81 ARCH="${{ matrix.arch }}"88 if [ -f emscripten-module.wasm ]; then
82 SRC="apps/cli/dist/executor-${PLATFORM}-${ARCH}/bin"89 cp emscripten-module.wasm apps/desktop/resources/
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/"
89 fi90 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 }
90 102
91 - name: Build desktop app (TypeScript)103 - name: Build desktop app (TypeScript)
92 run: bunx tsc -p tsconfig.json104 run: bunx tsc -p tsconfig.json
93 working-directory: apps/desktop105 working-directory: apps/desktop
94 106
95 - name: Build desktop distributables107 - name: Build desktop distributables
96 env:108 run: npx electron-builder --${{ matrix.platform }} --${{ matrix.arch }} --dir
97 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
98 run: |
99 npx electron-builder --${{ matrix.platform }} --${{ matrix.arch }} --dir
100 working-directory: apps/desktop109 working-directory: apps/desktop
101 110
102 - name: Package artifacts (macOS)111 - name: Package (macOS)
103 if: matrix.platform == 'mac'112 if: matrix.platform == 'mac'
104 working-directory: apps/desktop/dist113 working-directory: apps/desktop/dist
105 run: |114 run: |
106 # Find the .app and zip it
107 APP_DIR=$(find . -name "*.app" -maxdepth 2 | head -1)115 APP_DIR=$(find . -name "*.app" -maxdepth 2 | head -1)
108 if [ -n "$APP_DIR" ]; then116 if [ -n "$APP_DIR" ]; then
109 ZIP_NAME="Executor-${{ matrix.arch }}-mac.zip"117 ditto -c -k --sequesterRsrc --keepParent "$APP_DIR" "Executor-${{ matrix.arch }}-mac.zip"
110 ditto -c -k --sequesterRsrc --keepParent "$APP_DIR" "$ZIP_NAME"
111 fi118 fi
112 119
113 - name: Package artifacts (Linux)120 - name: Package (Linux)
114 if: matrix.platform == 'linux'121 if: matrix.platform == 'linux'
115 working-directory: apps/desktop/dist122 working-directory: apps/desktop/dist
116 run: |123 run: |
@@ -119,7 +126,7 @@ jobs: · 2 unmodified lines
119 tar -czf "Executor-x64-linux.tar.gz" -C "$UNPACKED" .126 tar -czf "Executor-x64-linux.tar.gz" -C "$UNPACKED" .
120 fi127 fi
121 128
122 - name: Package artifacts (Windows)129 - name: Package (Windows)
123 if: matrix.platform == 'win'130 if: matrix.platform == 'win'
124 working-directory: apps/desktop/dist131 working-directory: apps/desktop/dist
125 shell: pwsh132 shell: pwsh
@@ -134,8 +141,8 @@ jobs: · 8 unmodified lines
134 with:141 with:
135 name: desktop-${{ matrix.platform }}-${{ matrix.arch }}142 name: desktop-${{ matrix.platform }}-${{ matrix.arch }}
136 path: |143 path: |
137 apps/desktop/dist/*.zip144 apps/desktop/dist/Executor-*.zip
138 apps/desktop/dist/*.tar.gz145 apps/desktop/dist/Executor-*.tar.gz
139 if-no-files-found: warn146 if-no-files-found: warn
140 147
141 release:148 release:
@@ -149,26 +156,11 @@ jobs: · 7 unmodified lines
149 path: artifacts156 path: artifacts
150 merge-multiple: true157 merge-multiple: true
151 158
152 - name: List artifacts
153 run: find artifacts -type f | head -20
154
155 - name: Upload to GitHub Release159 - name: Upload to GitHub Release
156 env:160 env:
157 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}161 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
158 TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
159 run: |162 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
171 find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) | while read file; do163 find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) | while read file; do
172 echo "Uploading: $file"164 echo "Uploading: $file"
173 gh release upload "$TAG" "$file" --repo "$GITHUB_REPOSITORY" --clobber || true165 gh release upload "${{ inputs.tag }}" "$file" --repo "${{ github.repository }}" --clobber || true
174 done166 done
.github/workflows/publish-executor-package.yml+7−0
@@ -13,6 +13,7 @@ on: · 12 unmodified lines
13 type: string13 type: string
14 14
15permissions:15permissions:
16 actions: write
16 contents: write17 contents: write
17 id-token: write18 id-token: write
18 19
@@ -64,3 +65,9 @@ jobs: · 45 unmodified lines
64 export GITHUB_REF_NAME="$RELEASE_TAG"65 export GITHUB_REF_NAME="$RELEASE_TAG"
65 export GITHUB_REF="refs/tags/$RELEASE_TAG"66 export GITHUB_REF="refs/tags/$RELEASE_TAG"
66 bun run release:publish67 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
88 git tag "$tag"88 git tag "$tag"
89 git push origin "$tag"89 git push origin "$tag"
90 90
91 - name: Trigger publish workflows91 - name: Trigger CLI publish
92 if: steps.changesets.outputs.hasChangesets == 'false' && steps.detect_release.outputs.changed == 'true'92 if: steps.changesets.outputs.hasChangesets == 'false' && steps.detect_release.outputs.changed == 'true'
93 env:93 env:
94 GH_TOKEN: ${{ github.token }}94 GH_TOKEN: ${{ github.token }}
95 run: |95 run: |
96 tag="v${{ steps.detect_release.outputs.version }}"96 tag="v${{ steps.detect_release.outputs.version }}"
97 gh workflow run publish-executor-package.yml --ref "$tag" -f tag="$tag"97 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
10} from "electron";10} from "electron";
11import { spawn, type ChildProcess } from "node:child_process";11import { spawn, type ChildProcess } from "node:child_process";
12import { join, resolve, basename } from "node:path";12import { join, resolve, basename } from "node:path";
13import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";13import {
14 existsSync, readFileSync, writeFileSync, mkdirSync,
15 copyFileSync, chmodSync, appendFileSync,
16} from "node:fs";
14import { homedir } from "node:os";17import { homedir } from "node:os";
15 18
16// ---------------------------------------------------------------------------19// ---------------------------------------------------------------------------
@@ -22,6 +25,59 @@ const SERVER_STARTUP_TIMEOUT_MS = 30_000; · 5 unmodified lines
22const SETTINGS_DIR = join(homedir(), ".executor");25const SETTINGS_DIR = join(homedir(), ".executor");
23const SETTINGS_PATH = join(SETTINGS_DIR, "desktop-settings.json");26const SETTINGS_PATH = join(SETTINGS_DIR, "desktop-settings.json");
24 27
28const CLI_BIN_DIR = join(SETTINGS_DIR, "bin");
29const 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
35const 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
25// ---------------------------------------------------------------------------81// ---------------------------------------------------------------------------
26// Settings persistence82// Settings persistence
27// ---------------------------------------------------------------------------83// ---------------------------------------------------------------------------
@@ -630,6 +686,9 @@ app.whenReady().then(async () => { · 602 unmodified lines
630 // Clear cached web content so we always load the latest UI686 // Clear cached web content so we always load the latest UI
631 await session.defaultSession.clearCache();687 await session.defaultSession.clearCache();
632 688
689 // Install/update CLI binary to ~/.executor/bin
690 installCli();
691
633 settings = loadSettings();692 settings = loadSettings();
634 setupIPC();693 setupIPC();
635 buildMenu();694 buildMenu();