From c4fb2a425a64d8b9738fa676800909c7809b608d Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:53:26 +1000 Subject: [PATCH 1/2] fix(pty): upgrade bindings and honor packaging targets --- .github/workflows/pty.yml | 85 ++++++++++++++++ bun.lock | 40 ++++---- package.json | 2 +- packages/cli/package.json | 12 +-- packages/desktop/electron.vite.config.test.ts | 98 ++++++++++++------- packages/desktop/electron.vite.config.ts | 5 +- packages/desktop/package.json | 12 +-- packages/desktop/scripts/pty-smoke.mjs | 42 ++++++++ packages/desktop/scripts/target.ts | 53 ++++++++++ packages/desktop/scripts/utils.ts | 55 +---------- 10 files changed, 281 insertions(+), 123 deletions(-) create mode 100644 .github/workflows/pty.yml create mode 100644 packages/desktop/scripts/pty-smoke.mjs create mode 100644 packages/desktop/scripts/target.ts diff --git a/.github/workflows/pty.yml b/.github/workflows/pty.yml new file mode 100644 index 000000000000..a1264fe53c9e --- /dev/null +++ b/.github/workflows/pty.yml @@ -0,0 +1,85 @@ +name: pty + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/pty.yml + - package.json + - bun.lock + - packages/cli/package.json + - packages/core/package.json + - packages/core/script/fix-node-pty.ts + - packages/desktop/package.json + - packages/desktop/electron*.ts + - packages/desktop/scripts/** + pull_request: + paths: + - .github/workflows/pty.yml + - package.json + - bun.lock + - packages/cli/package.json + - packages/core/package.json + - packages/core/script/fix-node-pty.ts + - packages/desktop/package.json + - packages/desktop/electron*.ts + - packages/desktop/scripts/** + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + windows: + name: Windows ${{ matrix.arch }} native PTY + strategy: + fail-fast: false + matrix: + include: + - runner: windows-2025 + arch: x64 + - runner: windows-11-arm + arch: arm64 + runs-on: ${{ matrix.runner }} + timeout-minutes: 10 + defaults: + run: + shell: pwsh + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "24" + architecture: ${{ matrix.arch }} + - name: Install isolated native dependencies + env: + EXPECTED_ARCH: ${{ matrix.arch }} + run: | + node -e 'require("node:assert/strict").equal(process.arch, process.env.EXPECTED_ARCH)' + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $root = Get-Content package.json -Raw | ConvertFrom-Json + $desktop = Get-Content packages/desktop/package.json -Raw | ConvertFrom-Json + $package = node --input-type=module -e 'import { getCurrentCli } from "./packages/desktop/scripts/target.ts"; const target = getCurrentCli(); console.log(`@lydell/node-pty-${target.os}-${target.cpu}`)' + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $deps = Join-Path $env:RUNNER_TEMP pty-smoke + npm install --prefix $deps --no-audit --no-fund --package-lock=false "@lydell/node-pty@$($root.workspaces.catalog.'@lydell/node-pty')" "${package}@$($desktop.optionalDependencies.$package)" "electron@$($desktop.devDependencies.electron)" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + node "$deps/node_modules/electron/install.js" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + "PTY_SMOKE_DEPS=$deps" >> $env:GITHUB_ENV + "PTY_SMOKE_PACKAGE=$package" >> $env:GITHUB_ENV + - name: Verify Node native loading + working-directory: packages/desktop + run: node scripts/pty-smoke.mjs $env:PTY_SMOKE_DEPS $env:PTY_SMOKE_PACKAGE '${{ matrix.arch }}' + - name: Verify Electron native loading + working-directory: packages/desktop + env: + ELECTRON_RUN_AS_NODE: "1" + run: | + & "$env:PTY_SMOKE_DEPS/node_modules/electron/dist/electron.exe" scripts/pty-smoke.mjs $env:PTY_SMOKE_DEPS $env:PTY_SMOKE_PACKAGE '${{ matrix.arch }}' diff --git a/bun.lock b/bun.lock index e43b4fd8a814..d14111f8d6ee 100644 --- a/bun.lock +++ b/bun.lock @@ -143,12 +143,12 @@ "@ff-labs/fff-bin-linux-x64-gnu": "0.10.5", "@ff-labs/fff-bin-win32-arm64": "0.10.5", "@ff-labs/fff-bin-win32-x64": "0.10.5", - "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", - "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", - "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", - "@lydell/node-pty-linux-x64": "1.2.0-beta.12", - "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", - "@lydell/node-pty-win32-x64": "1.2.0-beta.12", + "@lydell/node-pty-darwin-arm64": "1.2.0-beta.15", + "@lydell/node-pty-darwin-x64": "1.2.0-beta.15", + "@lydell/node-pty-linux-arm64": "1.2.0-beta.15", + "@lydell/node-pty-linux-x64": "1.2.0-beta.15", + "@lydell/node-pty-win32-arm64": "1.2.0-beta.15", + "@lydell/node-pty-win32-x64": "1.2.0-beta.15", "@opencode-ai/protocol": "workspace:*", "@opencode-ai/script": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", @@ -440,12 +440,12 @@ "vite": "8.2.2", }, "optionalDependencies": { - "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", - "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", - "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", - "@lydell/node-pty-linux-x64": "1.2.0-beta.12", - "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", - "@lydell/node-pty-win32-x64": "1.2.0-beta.12", + "@lydell/node-pty-darwin-arm64": "1.2.0-beta.15", + "@lydell/node-pty-darwin-x64": "1.2.0-beta.15", + "@lydell/node-pty-linux-arm64": "1.2.0-beta.15", + "@lydell/node-pty-linux-x64": "1.2.0-beta.15", + "@lydell/node-pty-win32-arm64": "1.2.0-beta.15", + "@lydell/node-pty-win32-x64": "1.2.0-beta.15", "msgpackr-extract": "3.0.4", }, }, @@ -1068,7 +1068,7 @@ "@hono/standard-validator": "0.2.0", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.13", - "@lydell/node-pty": "1.2.0-beta.12", + "@lydell/node-pty": "1.2.0-beta.15", "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", @@ -1953,19 +1953,19 @@ "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="], - "@lydell/node-pty": ["@lydell/node-pty@1.2.0-beta.12", "", { "optionalDependencies": { "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", "@lydell/node-pty-linux-x64": "1.2.0-beta.12", "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", "@lydell/node-pty-win32-x64": "1.2.0-beta.12" } }, "sha512-qIK890UwPupoj07osVvgOIa++1mxeHbcGry4PKRHhNVNs81V2SCG34eJr46GybiOmBtc8Sj5PB1/GGM5PL549g=="], + "@lydell/node-pty": ["@lydell/node-pty@1.2.0-beta.15", "", { "optionalDependencies": { "@lydell/node-pty-darwin-arm64": "1.2.0-beta.15", "@lydell/node-pty-darwin-x64": "1.2.0-beta.15", "@lydell/node-pty-linux-arm64": "1.2.0-beta.15", "@lydell/node-pty-linux-x64": "1.2.0-beta.15", "@lydell/node-pty-win32-arm64": "1.2.0-beta.15", "@lydell/node-pty-win32-x64": "1.2.0-beta.15" } }, "sha512-Br8wBxzbxFwdWgk9uQ+rdzE0xfoxOK4QuGH54swhRwc5IxP6H9Y1/bcyazRGvNUs6XkB5qNVkezuKSRxUwZe7A=="], - "@lydell/node-pty-darwin-arm64": ["@lydell/node-pty-darwin-arm64@1.2.0-beta.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tqaifcY9Cr41SblO1+FLzh8oxxtkNhuW9Dhl22lKme9BreYvKvxEZcdPIXTuqkJc5tagOEC4QHShKmJjLyLXLQ=="], + "@lydell/node-pty-darwin-arm64": ["@lydell/node-pty-darwin-arm64@1.2.0-beta.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6TSBbzdcLiNTHl1mTuzflqXrkmcC36USVGvERoDgvHk2ItEDaMaFZuAJ1CqPmwYj0DyhCS16TVS8OGK9xZnjyQ=="], - "@lydell/node-pty-darwin-x64": ["@lydell/node-pty-darwin-x64@1.2.0-beta.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-4LrS5pCJwqHKDVf1zS2gyNV0m4hKAXch+XZNhbZ6LY8uwVL8BhchzQBO40Os5anuRxRCWzHpw4Sp64Ie8q7E4Q=="], + "@lydell/node-pty-darwin-x64": ["@lydell/node-pty-darwin-x64@1.2.0-beta.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-yDT2oqPqYMBScyuk1U9Rg5VKcrbMOD9o9jWYYamDADA3NSbUISroPChrqYRQ74Y7BQtNH4gqYAiWOZRi5uQZ0Q=="], - "@lydell/node-pty-linux-arm64": ["@lydell/node-pty-linux-arm64@1.2.0-beta.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-Sx+A71x5BDGHt9ansfrtGxwq2VFVDWvJUAdlUL0Hv0qeiJUfts+hgopx+CgT4PSwahKjdEgtu0+FAfY9rICKRw=="], + "@lydell/node-pty-linux-arm64": ["@lydell/node-pty-linux-arm64@1.2.0-beta.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-wkbNF7dYAmtJv+o2+iztVlNwnUB4B0uX0wh/UD+mwMcmE2gNMnW9GChXO7fEE5XJokD0vB5idiHpGegaN+G/sg=="], - "@lydell/node-pty-linux-x64": ["@lydell/node-pty-linux-x64@1.2.0-beta.12", "", { "os": "linux", "cpu": "x64" }, "sha512-bJzs94njofYhGg/UDqW1nj0dtvvu+2OvxMY+RlLS1T17VgcktKoIR6PuenTwE5HJ/D6StCPADmXcT0nNsCKmIQ=="], + "@lydell/node-pty-linux-x64": ["@lydell/node-pty-linux-x64@1.2.0-beta.15", "", { "os": "linux", "cpu": "x64" }, "sha512-+U/5AVvHT6W+8OCYcnJgN0Qgc0ycO3TfD6aaFJHK+WHij797f8gsi5dV1HEO9l6YQmWCD+VL5gaLDhx3mxHwCA=="], - "@lydell/node-pty-win32-arm64": ["@lydell/node-pty-win32-arm64@1.2.0-beta.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-p7POgjVEiFaBC3/y+AKuV1FzePCsJ6HmZDv2XK+jBZSfwP8+uBAw181ZiKYN1YuRa/XpmBGaWezcI8hZkbW++g=="], + "@lydell/node-pty-win32-arm64": ["@lydell/node-pty-win32-arm64@1.2.0-beta.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-pyAk91w7wnnKrD4mrHXtIXRfmzSWV5bEzvRhurXcMCtCc2TJ424ciUskIgWMhAPP6y3KyUnqElj+U6kY3iOt0A=="], - "@lydell/node-pty-win32-x64": ["@lydell/node-pty-win32-x64@1.2.0-beta.12", "", { "os": "win32", "cpu": "x64" }, "sha512-IDFa00g7qUDGUYgByrUBJtC+mOjYVt/8KYyWivCg5JjGOHbBUACUQZLl0jTWmnr+tld/UyTpX90a2PY6oTVtRw=="], + "@lydell/node-pty-win32-x64": ["@lydell/node-pty-win32-x64@1.2.0-beta.15", "", { "os": "win32", "cpu": "x64" }, "sha512-2f8twEmDVxZ7drchAXjtevpmSPhFok0avAnzXro4t5gmz0xsPNKkoZvymwtuIS3xo7PzQqZOPQ/YzwEMb7oIzQ=="], "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], diff --git a/package.json b/package.json index 5e69c93016e0..daa3eeee7e8f 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "solid-js": "1.9.10", "solid-sonner": "0.3.1", "vite-plugin-solid": "2.11.10", - "@lydell/node-pty": "1.2.0-beta.12" + "@lydell/node-pty": "1.2.0-beta.15" } }, "devDependencies": { diff --git a/packages/cli/package.json b/packages/cli/package.json index deac1fa518d5..84f605e31ed3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -53,12 +53,12 @@ "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", "@typescript/native-preview": "catalog:", - "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", - "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", - "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", - "@lydell/node-pty-linux-x64": "1.2.0-beta.12", - "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", - "@lydell/node-pty-win32-x64": "1.2.0-beta.12", + "@lydell/node-pty-darwin-arm64": "1.2.0-beta.15", + "@lydell/node-pty-darwin-x64": "1.2.0-beta.15", + "@lydell/node-pty-linux-arm64": "1.2.0-beta.15", + "@lydell/node-pty-linux-x64": "1.2.0-beta.15", + "@lydell/node-pty-win32-arm64": "1.2.0-beta.15", + "@lydell/node-pty-win32-x64": "1.2.0-beta.15", "@ff-labs/fff-bin-darwin-arm64": "0.10.5", "@ff-labs/fff-bin-linux-arm64-gnu": "0.10.5", "@ff-labs/fff-bin-linux-x64-gnu": "0.10.5", diff --git a/packages/desktop/electron.vite.config.test.ts b/packages/desktop/electron.vite.config.test.ts index 0abdff38f496..0ea5bdca6604 100644 --- a/packages/desktop/electron.vite.config.test.ts +++ b/packages/desktop/electron.vite.config.test.ts @@ -1,7 +1,17 @@ -import { expect, test } from "bun:test" +import { afterEach, expect, test } from "bun:test" import { loadConfigFromFile, MainConfigFactory } from "electron-vite" import { build } from "vite" import pkg from "./package.json" +import { getCurrentCli } from "./scripts/target" + +const originalTarget = process.env.OPENCODE_CLI_TARGET +afterEach(() => { + if (originalTarget === undefined) { + delete process.env.OPENCODE_CLI_TARGET + return + } + process.env.OPENCODE_CLI_TARGET = originalTarget +}) test.each(["build", "serve"] as const)("configures minification for %s", async (command) => { const result = await loadConfigFromFile( @@ -43,37 +53,55 @@ test("keeps PTY binaries without stale native packaging", () => { ]) }) -test("bundles one Effect runtime and Drizzle while keeping native dependencies external", async () => { - const result = await loadConfigFromFile( - { command: "build", mode: "production" }, - `${import.meta.dirname}/electron.vite.config.ts`, - ) - if (!result.config.main) throw new Error("Missing main-process build configuration") - const config = await new MainConfigFactory( - result.config.main, - { configFile: false, mode: "production" }, - { root: import.meta.dirname }, - ).build() - config.build = { ...config.build, write: false } - config.logLevel = "silent" - const output = await build(config) - const chunks = (Array.isArray(output) ? output : [output]).flatMap((result) => - "output" in result ? result.output.filter((item) => item.type === "chunk") : [], - ) - expect(chunks.length).toBeGreaterThan(0) - const imports = chunks.flatMap((chunk) => [...chunk.imports, ...chunk.dynamicImports]) - const modules = chunks.flatMap((chunk) => Object.keys(chunk.modules)) - for (const name of ["effect", "@effect/platform-node", "@effect/platform-node-shared", "drizzle-orm"]) { - expect(imports.filter((id) => id === name || id.startsWith(`${name}/`))).toEqual([]) - expect(modules.some((id) => id.includes(`/node_modules/${name}/`))).toBe(true) - } - const effect = modules.filter((id) => id.includes("/node_modules/effect/")) - expect(new Set(effect.map((id) => id.split("/node_modules/effect/")[0])).size).toBe(1) - expect(new Set(effect).size).toBe(effect.length) - expect(imports).toContain("electron") - expect(imports).toContain("node:sqlite") - expect(chunks.some((chunk) => chunk.dynamicImports.includes("@zip.js/zip.js"))).toBe(true) - expect(imports).toContain(`@lydell/node-pty-${process.platform}-${process.arch}`) - expect(modules.some((id) => id.includes("/node_modules/msgpackr-extract/"))).toBe(false) - expect(chunks.some((chunk) => chunk.code.includes("msgpackr-extract"))).toBe(true) -}, 30_000) +test("rejects unsupported packaging targets", () => { + expect(() => getCurrentCli("unsupported-target")).toThrow("CLI configuration not available") +}) + +test.each([ + [undefined, `${process.platform}-${process.arch}`], + ["aarch64-apple-darwin", "darwin-arm64"], + ["x86_64-apple-darwin", "darwin-x64"], + ["aarch64-pc-windows-msvc", "win32-arm64"], + ["x86_64-pc-windows-msvc", "win32-x64"], + ["aarch64-unknown-linux-gnu", "linux-arm64"], + ["x86_64-unknown-linux-gnu", "linux-x64"], +])( + "bundles shared runtimes and external PTY bindings for %s (%s)", + async (target, binding) => { + process.env.OPENCODE_CLI_TARGET = target + if (target === undefined) delete process.env.OPENCODE_CLI_TARGET + const result = await loadConfigFromFile( + { command: "build", mode: "production" }, + `${import.meta.dirname}/electron.vite.config.ts`, + ) + if (!result.config.main) throw new Error("Missing main-process build configuration") + const config = await new MainConfigFactory( + result.config.main, + { configFile: false, mode: "production" }, + { root: import.meta.dirname }, + ).build() + config.build = { ...config.build, write: false } + config.logLevel = "silent" + const output = await build(config) + const chunks = (Array.isArray(output) ? output : [output]).flatMap((result) => + "output" in result ? result.output.filter((item) => item.type === "chunk") : [], + ) + expect(chunks.length).toBeGreaterThan(0) + const imports = chunks.flatMap((chunk) => [...chunk.imports, ...chunk.dynamicImports]) + const modules = chunks.flatMap((chunk) => Object.keys(chunk.modules)) + for (const name of ["effect", "@effect/platform-node", "@effect/platform-node-shared", "drizzle-orm"]) { + expect(imports.filter((id) => id === name || id.startsWith(`${name}/`))).toEqual([]) + expect(modules.some((id) => id.includes(`/node_modules/${name}/`))).toBe(true) + } + const effect = modules.filter((id) => id.includes("/node_modules/effect/")) + expect(new Set(effect.map((id) => id.split("/node_modules/effect/")[0])).size).toBe(1) + expect(new Set(effect).size).toBe(effect.length) + expect(imports).toContain("electron") + expect(imports).toContain("node:sqlite") + expect(chunks.some((chunk) => chunk.dynamicImports.includes("@zip.js/zip.js"))).toBe(true) + expect(imports.filter((id) => id.startsWith("@lydell/node-pty"))).toEqual([`@lydell/node-pty-${binding}`]) + expect(modules.some((id) => id.includes("/node_modules/msgpackr-extract/"))).toBe(false) + expect(chunks.some((chunk) => chunk.code.includes("msgpackr-extract"))).toBe(true) + }, + 30_000, +) diff --git a/packages/desktop/electron.vite.config.ts b/packages/desktop/electron.vite.config.ts index d62fe67e3c52..dcfc82ae20da 100644 --- a/packages/desktop/electron.vite.config.ts +++ b/packages/desktop/electron.vite.config.ts @@ -1,4 +1,5 @@ import { defineConfig } from "electron-vite" +import { getCurrentCli } from "./scripts/target" const channel = (() => { const raw = process.env.OPENCODE_CHANNEL @@ -7,7 +8,9 @@ const channel = (() => { return "dev" })() -const nodePtyPkg = `@lydell/node-pty-${process.platform}-${process.arch}` +// Windows ARM64 releases are bundled on x64 hosts. +const target = getCurrentCli() +const nodePtyPkg = `@lydell/node-pty-${target.os}-${target.cpu}` const appPlugin = (await import("@opencode-ai/app/vite")).default const picker = (await import("@brendonovich/vite-plugin-opencode")).default() diff --git a/packages/desktop/package.json b/packages/desktop/package.json index ecb8b11ed4e0..e1feddfa3973 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -54,12 +54,12 @@ "vite": "8.2.2" }, "optionalDependencies": { - "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", - "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", - "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", - "@lydell/node-pty-linux-x64": "1.2.0-beta.12", - "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", - "@lydell/node-pty-win32-x64": "1.2.0-beta.12", + "@lydell/node-pty-darwin-arm64": "1.2.0-beta.15", + "@lydell/node-pty-darwin-x64": "1.2.0-beta.15", + "@lydell/node-pty-linux-arm64": "1.2.0-beta.15", + "@lydell/node-pty-linux-x64": "1.2.0-beta.15", + "@lydell/node-pty-win32-arm64": "1.2.0-beta.15", + "@lydell/node-pty-win32-x64": "1.2.0-beta.15", "msgpackr-extract": "3.0.4" } } diff --git a/packages/desktop/scripts/pty-smoke.mjs b/packages/desktop/scripts/pty-smoke.mjs new file mode 100644 index 000000000000..693346fd8fed --- /dev/null +++ b/packages/desktop/scripts/pty-smoke.mjs @@ -0,0 +1,42 @@ +import assert from "node:assert/strict" +import { createRequire } from "node:module" +import path from "node:path" + +assert.equal(process.platform, "win32") +assert.equal(process.arch, process.argv[4], "The runtime must use the runner's native architecture") +assert.equal(process.argv[3], `@lydell/node-pty-${process.platform}-${process.arch}`) + +// Resolve only from the isolated installation, not the monorepo's dependencies. +const require = createRequire(path.resolve(process.argv[2], "package.json")) + +for (const name of ["@lydell/node-pty", process.argv[3]]) { + const terminal = require(name).spawn("cmd.exe", ["/d", "/c", "echo opencode-pty-smoke&exit /b 23"], { + name: "xterm-256color", + cols: 80, + rows: 24, + cwd: process.cwd(), + env: process.env, + }) + const chunks = [] + const done = Promise.withResolvers() + const data = terminal.onData((chunk) => chunks.push(chunk)) + const exit = terminal.onExit(done.resolve) + const timer = setTimeout(() => { + done.reject(new Error(`${name}: PTY did not exit within 15 seconds`)) + }, 15_000) + + try { + const result = await done.promise + assert.equal(result.exitCode, 23, `${name}: unexpected exit status`) + assert.ok(chunks.join("").includes("opencode-pty-smoke"), `${name}: missing PTY output`) + console.log( + `${process.versions.electron ? `Electron ${process.versions.electron}` : `Node ${process.version}`} ${process.arch}: ${name} passed`, + ) + } finally { + clearTimeout(timer) + data.dispose() + exit.dispose() + // Release ConPTY's worker and handles even after the shell has exited. + terminal.kill() + } +} diff --git a/packages/desktop/scripts/target.ts b/packages/desktop/scripts/target.ts new file mode 100644 index 000000000000..9ae4e90cc1b6 --- /dev/null +++ b/packages/desktop/scripts/target.ts @@ -0,0 +1,53 @@ +const CLI_BINARIES: Array<{ target: string; package: string; os: string; cpu: string }> = [ + { + target: "aarch64-apple-darwin", + package: "@opencode-ai/cli-darwin-arm64", + os: "darwin", + cpu: "arm64", + }, + { + target: "x86_64-apple-darwin", + package: "@opencode-ai/cli-darwin-x64-baseline", + os: "darwin", + cpu: "x64", + }, + { + target: "aarch64-pc-windows-msvc", + package: "@opencode-ai/cli-windows-arm64", + os: "win32", + cpu: "arm64", + }, + { + target: "x86_64-pc-windows-msvc", + package: "@opencode-ai/cli-windows-x64-baseline", + os: "win32", + cpu: "x64", + }, + { + target: "x86_64-unknown-linux-gnu", + package: "@opencode-ai/cli-linux-x64-baseline", + os: "linux", + cpu: "x64", + }, + { + target: "aarch64-unknown-linux-gnu", + package: "@opencode-ai/cli-linux-arm64", + os: "linux", + cpu: "arm64", + }, +] + +export function getCurrentCli(target = process.env.OPENCODE_CLI_TARGET ?? nativeTarget()) { + const binaryConfig = CLI_BINARIES.find((item) => item.target === target) + if (!binaryConfig) throw new Error(`CLI configuration not available for target '${target}'`) + + return binaryConfig +} + +function nativeTarget() { + const { platform, arch } = process + if (platform === "darwin") return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin" + if (platform === "win32") return arch === "arm64" ? "aarch64-pc-windows-msvc" : "x86_64-pc-windows-msvc" + if (platform === "linux") return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu" + throw new Error(`Unsupported platform: ${platform}/${arch}`) +} diff --git a/packages/desktop/scripts/utils.ts b/packages/desktop/scripts/utils.ts index ad356fce50d5..062598a8ad60 100644 --- a/packages/desktop/scripts/utils.ts +++ b/packages/desktop/scripts/utils.ts @@ -2,6 +2,7 @@ import { $ } from "bun" import { chmod, copyFile, mkdtemp, rm } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" +import { getCurrentCli } from "./target" const CLI_VERSION = "dev" @@ -13,62 +14,8 @@ export function resolveChannel(): Channel { return "dev" } -export const CLI_BINARIES: Array<{ target: string; package: string; os: string; cpu: string }> = [ - { - target: "aarch64-apple-darwin", - package: "@opencode-ai/cli-darwin-arm64", - os: "darwin", - cpu: "arm64", - }, - { - target: "x86_64-apple-darwin", - package: "@opencode-ai/cli-darwin-x64-baseline", - os: "darwin", - cpu: "x64", - }, - { - target: "aarch64-pc-windows-msvc", - package: "@opencode-ai/cli-windows-arm64", - os: "win32", - cpu: "arm64", - }, - { - target: "x86_64-pc-windows-msvc", - package: "@opencode-ai/cli-windows-x64-baseline", - os: "win32", - cpu: "x64", - }, - { - target: "x86_64-unknown-linux-gnu", - package: "@opencode-ai/cli-linux-x64-baseline", - os: "linux", - cpu: "x64", - }, - { - target: "aarch64-unknown-linux-gnu", - package: "@opencode-ai/cli-linux-arm64", - os: "linux", - cpu: "arm64", - }, -] - export const CLI_TARGET = Bun.env.OPENCODE_CLI_TARGET -function nativeTarget() { - const { platform, arch } = process - if (platform === "darwin") return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin" - if (platform === "win32") return arch === "arm64" ? "aarch64-pc-windows-msvc" : "x86_64-pc-windows-msvc" - if (platform === "linux") return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu" - throw new Error(`Unsupported platform: ${platform}/${arch}`) -} - -export function getCurrentCli(target = CLI_TARGET ?? nativeTarget()) { - const binaryConfig = CLI_BINARIES.find((item) => item.target === target) - if (!binaryConfig) throw new Error(`CLI configuration not available for target '${target}'`) - - return binaryConfig -} - export async function downloadCliToResources(version = CLI_VERSION, dest = windowsify("resources/opencode-cli")) { const cli = getCurrentCli() const directory = await mkdtemp(join(tmpdir(), "opencode-cli-")) From 735911345b232be2fd23bc8c4e19bf031406adc7 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:57:43 +1000 Subject: [PATCH 2/2] chore(pty): remove one-off verification workflow --- .github/workflows/pty.yml | 85 -------------------------- packages/desktop/scripts/pty-smoke.mjs | 42 ------------- 2 files changed, 127 deletions(-) delete mode 100644 .github/workflows/pty.yml delete mode 100644 packages/desktop/scripts/pty-smoke.mjs diff --git a/.github/workflows/pty.yml b/.github/workflows/pty.yml deleted file mode 100644 index a1264fe53c9e..000000000000 --- a/.github/workflows/pty.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: pty - -on: - workflow_dispatch: - push: - paths: - - .github/workflows/pty.yml - - package.json - - bun.lock - - packages/cli/package.json - - packages/core/package.json - - packages/core/script/fix-node-pty.ts - - packages/desktop/package.json - - packages/desktop/electron*.ts - - packages/desktop/scripts/** - pull_request: - paths: - - .github/workflows/pty.yml - - package.json - - bun.lock - - packages/cli/package.json - - packages/core/package.json - - packages/core/script/fix-node-pty.ts - - packages/desktop/package.json - - packages/desktop/electron*.ts - - packages/desktop/scripts/** - -permissions: - contents: read - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - windows: - name: Windows ${{ matrix.arch }} native PTY - strategy: - fail-fast: false - matrix: - include: - - runner: windows-2025 - arch: x64 - - runner: windows-11-arm - arch: arm64 - runs-on: ${{ matrix.runner }} - timeout-minutes: 10 - defaults: - run: - shell: pwsh - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: "24" - architecture: ${{ matrix.arch }} - - name: Install isolated native dependencies - env: - EXPECTED_ARCH: ${{ matrix.arch }} - run: | - node -e 'require("node:assert/strict").equal(process.arch, process.env.EXPECTED_ARCH)' - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - $root = Get-Content package.json -Raw | ConvertFrom-Json - $desktop = Get-Content packages/desktop/package.json -Raw | ConvertFrom-Json - $package = node --input-type=module -e 'import { getCurrentCli } from "./packages/desktop/scripts/target.ts"; const target = getCurrentCli(); console.log(`@lydell/node-pty-${target.os}-${target.cpu}`)' - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - $deps = Join-Path $env:RUNNER_TEMP pty-smoke - npm install --prefix $deps --no-audit --no-fund --package-lock=false "@lydell/node-pty@$($root.workspaces.catalog.'@lydell/node-pty')" "${package}@$($desktop.optionalDependencies.$package)" "electron@$($desktop.devDependencies.electron)" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - node "$deps/node_modules/electron/install.js" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - "PTY_SMOKE_DEPS=$deps" >> $env:GITHUB_ENV - "PTY_SMOKE_PACKAGE=$package" >> $env:GITHUB_ENV - - name: Verify Node native loading - working-directory: packages/desktop - run: node scripts/pty-smoke.mjs $env:PTY_SMOKE_DEPS $env:PTY_SMOKE_PACKAGE '${{ matrix.arch }}' - - name: Verify Electron native loading - working-directory: packages/desktop - env: - ELECTRON_RUN_AS_NODE: "1" - run: | - & "$env:PTY_SMOKE_DEPS/node_modules/electron/dist/electron.exe" scripts/pty-smoke.mjs $env:PTY_SMOKE_DEPS $env:PTY_SMOKE_PACKAGE '${{ matrix.arch }}' diff --git a/packages/desktop/scripts/pty-smoke.mjs b/packages/desktop/scripts/pty-smoke.mjs deleted file mode 100644 index 693346fd8fed..000000000000 --- a/packages/desktop/scripts/pty-smoke.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import assert from "node:assert/strict" -import { createRequire } from "node:module" -import path from "node:path" - -assert.equal(process.platform, "win32") -assert.equal(process.arch, process.argv[4], "The runtime must use the runner's native architecture") -assert.equal(process.argv[3], `@lydell/node-pty-${process.platform}-${process.arch}`) - -// Resolve only from the isolated installation, not the monorepo's dependencies. -const require = createRequire(path.resolve(process.argv[2], "package.json")) - -for (const name of ["@lydell/node-pty", process.argv[3]]) { - const terminal = require(name).spawn("cmd.exe", ["/d", "/c", "echo opencode-pty-smoke&exit /b 23"], { - name: "xterm-256color", - cols: 80, - rows: 24, - cwd: process.cwd(), - env: process.env, - }) - const chunks = [] - const done = Promise.withResolvers() - const data = terminal.onData((chunk) => chunks.push(chunk)) - const exit = terminal.onExit(done.resolve) - const timer = setTimeout(() => { - done.reject(new Error(`${name}: PTY did not exit within 15 seconds`)) - }, 15_000) - - try { - const result = await done.promise - assert.equal(result.exitCode, 23, `${name}: unexpected exit status`) - assert.ok(chunks.join("").includes("opencode-pty-smoke"), `${name}: missing PTY output`) - console.log( - `${process.versions.electron ? `Electron ${process.versions.electron}` : `Node ${process.version}`} ${process.arch}: ${name} passed`, - ) - } finally { - clearTimeout(timer) - data.dispose() - exit.dispose() - // Release ConPTY's worker and handles even after the shell has exited. - terminal.kill() - } -}