Skip to content

Commit d55c42f

Browse files
committed
install CLI to PATH on launch, fix desktop CI to download pre-built CLI
1 parent 41f5dc2 commit d55c42f

4 files changed

Lines changed: 116 additions & 55 deletions

File tree

‎.github/workflows/publish-desktop.yml‎

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ name: Publish Desktop App
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,23 +25,27 @@ jobs:
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

4344
steps:
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:
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:
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:
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:
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‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ on:
1313
type: string
1414

1515
permissions:
16+
actions: write
1617
contents: write
1718
id-token: write
1819

@@ -64,3 +65,9 @@ jobs:
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‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,14 @@ jobs:
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‎

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import {
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;
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 () => {
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();

0 commit comments

Comments
 (0)