From 968b89defe886de06cf99ff421346ca4805a5bcc Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 9 Jul 2026 18:15:12 -0700 Subject: [PATCH] fix(ci): remove cross-os TypeScript loader bootstrap (#103195) --- ...nclaw-cross-os-release-checks-reusable.yml | 1 - package.json | 2 +- scripts/ci-changed-scope.mjs | 4 +- .../run-openclaw-cross-os-release-checks.sh | 68 +------------------ scripts/openclaw-cross-os-release-checks.ts | 4 +- src/scripts/ci-changed-scope.test.ts | 17 +++++ ...openclaw-cross-os-release-workflow.test.ts | 68 ++++++++++++++++--- 7 files changed, 82 insertions(+), 82 deletions(-) diff --git a/.github/workflows/openclaw-cross-os-release-checks-reusable.yml b/.github/workflows/openclaw-cross-os-release-checks-reusable.yml index a8ff9590f5de..be6c0954747d 100644 --- a/.github/workflows/openclaw-cross-os-release-checks-reusable.yml +++ b/.github/workflows/openclaw-cross-os-release-checks-reusable.yml @@ -194,7 +194,6 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" NODE_VERSION: "24.15.0" OPENCLAW_REPOSITORY: openclaw/openclaw - TSX_VERSION: "4.21.0" OPENCLAW_CROSS_OS_OPENAI_MODEL: ${{ inputs.openai_model || vars.OPENCLAW_CROSS_OS_OPENAI_MODEL || 'openai/gpt-5.5' }} jobs: diff --git a/package.json b/package.json index 10cf207dc121..3a11d0056165 100644 --- a/package.json +++ b/package.json @@ -1942,7 +1942,7 @@ "test:unit:fast:audit": "node scripts/test-unit-fast-audit.mjs", "test:voicecall:closedloop": "node scripts/test-voicecall-closedloop.mjs", "test:watch": "node scripts/test-projects.mjs --watch", - "test:windows:ci": "node scripts/test-projects.mjs src/shared/runtime-import.test.ts src/process/exec.windows.test.ts src/process/windows-command.test.ts src/infra/backup-create.windows.test.ts src/infra/windows-install-roots.test.ts src/node-host/invoke-system-run-allowlist.test.ts src/daemon/schtasks.startup-fallback.test.ts extensions/lobster/src/lobster-runner.test.ts test/scripts/format-generated-module.test.ts test/scripts/npm-runner.test.ts test/scripts/pnpm-runner.test.ts test/scripts/run-with-env.test.ts test/scripts/ui.test.ts test/scripts/vitest-process-group.test.ts", + "test:windows:ci": "node scripts/test-projects.mjs src/shared/runtime-import.test.ts src/process/exec.windows.test.ts src/process/windows-command.test.ts src/infra/backup-create.windows.test.ts src/infra/windows-install-roots.test.ts src/node-host/invoke-system-run-allowlist.test.ts src/daemon/schtasks.startup-fallback.test.ts extensions/lobster/src/lobster-runner.test.ts test/scripts/format-generated-module.test.ts test/scripts/npm-runner.test.ts test/scripts/openclaw-cross-os-release-workflow.test.ts test/scripts/pnpm-runner.test.ts test/scripts/run-with-env.test.ts test/scripts/ui.test.ts test/scripts/vitest-process-group.test.ts", "tool-display:check": "node --import tsx scripts/tool-display.ts --check", "tool-display:write": "node --import tsx scripts/tool-display.ts --write", "ts-topology": "node --import tsx scripts/ts-topology.ts", diff --git a/scripts/ci-changed-scope.mjs b/scripts/ci-changed-scope.mjs index ddc22a47c0ae..ef1747ee1cd6 100644 --- a/scripts/ci-changed-scope.mjs +++ b/scripts/ci-changed-scope.mjs @@ -45,9 +45,9 @@ const ANDROID_NATIVE_RE = /^(apps\/android\/|apps\/shared\/)/; const NODE_SCOPE_RE = /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|\.github\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.oxlintrc\.json$|\.oxfmtrc\.jsonc$)/; const WINDOWS_SCOPE_RE = - /^(src\/process\/|src\/infra\/windows-install-roots\.ts$|src\/shared\/(?:import-specifier|runtime-import)(?:\.test)?\.ts$|scripts\/(?:install\.ps1|(?:npm-runner|pnpm-runner|ui|vitest-process-group)\.(?:mjs|js)|lib\/format-generated-module\.mjs)$|test\/scripts\/(?:format-generated-module|install-ps1|npm-runner|pnpm-runner|ui|vitest-process-group)\.test\.ts$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|\.github\/workflows\/ci\.yml$|\.github\/actions\/setup-node-env\/action\.yml$|\.github\/actions\/setup-pnpm-store-cache\/action\.yml$)/; + /^(src\/process\/|src\/infra\/windows-install-roots\.ts$|src\/shared\/(?:import-specifier|runtime-import)(?:\.test)?\.ts$|scripts\/(?:install\.ps1|openclaw-cross-os-release-checks\.ts|github\/run-openclaw-cross-os-release-checks\.sh|(?:npm-runner|pnpm-runner|ui|vitest-process-group)\.(?:mjs|js)|lib\/format-generated-module\.mjs)$|test\/scripts\/(?:format-generated-module|install-ps1|npm-runner|openclaw-cross-os-release-workflow|pnpm-runner|ui|vitest-process-group)\.test\.ts$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|\.github\/workflows\/(?:ci|openclaw-cross-os-release-checks-reusable)\.yml$|\.github\/actions\/setup-node-env\/action\.yml$|\.github\/actions\/setup-pnpm-store-cache\/action\.yml$)/; const WINDOWS_TEST_SCOPE_RE = - /^(src\/process\/(?:exec\.windows|windows-command)\.test\.ts$|src\/infra\/windows-install-roots\.test\.ts$|src\/shared\/runtime-import\.test\.ts$|test\/scripts\/(?:format-generated-module|npm-runner|pnpm-runner|ui|vitest-process-group)\.test\.ts$)/; + /^(src\/process\/(?:exec\.windows|windows-command)\.test\.ts$|src\/infra\/windows-install-roots\.test\.ts$|src\/shared\/runtime-import\.test\.ts$|test\/scripts\/(?:format-generated-module|npm-runner|openclaw-cross-os-release-workflow|pnpm-runner|ui|vitest-process-group)\.test\.ts$)/; const WINDOWS_DAEMON_SCOPE_RE = /^src\/daemon\/(?:schtasks(?:[-.][^/]+)?|runtime-hints\.windows-paths(?:\.test)?|test-helpers\/schtasks-(?:base-mocks|fixtures))\.ts$/; const TEST_ONLY_PATH_RE = diff --git a/scripts/github/run-openclaw-cross-os-release-checks.sh b/scripts/github/run-openclaw-cross-os-release-checks.sh index 783d3b9c7ab7..bd12f907c285 100755 --- a/scripts/github/run-openclaw-cross-os-release-checks.sh +++ b/scripts/github/run-openclaw-cross-os-release-checks.sh @@ -1,10 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -tsx_version="${OPENCLAW_RELEASE_TSX_VERSION:-${TSX_VERSION:-4.21.0}}" script_path="${OPENCLAW_RELEASE_CHECKS_SCRIPT:-workflow/scripts/openclaw-cross-os-release-checks.ts}" -if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then +if ! command -v node >/dev/null 2>&1; then if command -v cygpath >/dev/null 2>&1; then for node_dir in /c/hostedtoolcache/windows/node/*/x64 /c/actions-runner/_work/_tool/node/*/x64; do if [[ -x "${node_dir}/node.exe" ]]; then @@ -16,78 +15,15 @@ if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then fi node_cmd="node" -npm_cmd="npm" -npm_cli_js="" if command -v cygpath >/dev/null 2>&1; then if command -v node.exe >/dev/null 2>&1; then node_cmd="node.exe" - node_path="$(command -v node.exe)" - node_dir="$(dirname "$(cygpath -u "${node_path}")")" - if [[ -f "${node_dir}/node_modules/npm/bin/npm-cli.js" ]]; then - npm_cli_js="${node_dir}/node_modules/npm/bin/npm-cli.js" - fi fi - if command -v npm.cmd >/dev/null 2>&1; then - npm_cmd="npm.cmd" - elif command -v npm.exe >/dev/null 2>&1; then - npm_cmd="npm.exe" - fi -fi - -temp_root="${OPENCLAW_RELEASE_TSX_TOOL_ROOT:-${RUNNER_TEMP:-${TMPDIR:-/tmp}}}" -if command -v cygpath >/dev/null 2>&1; then - temp_root="$(cygpath -u "${temp_root}")" -fi - -tool_dir="${OPENCLAW_RELEASE_TSX_TOOL_DIR:-${temp_root}/openclaw-release-tsx-${tsx_version}}" -loader_path="${tool_dir}/node_modules/tsx/dist/loader.mjs" -npm_tool_dir="${tool_dir}" -npm_cli_arg="${npm_cli_js}" -loader_arg="${loader_path}" -if command -v cygpath >/dev/null 2>&1; then - npm_tool_dir="$(cygpath -w "${tool_dir}")" - if [[ -n "${npm_cli_js}" ]]; then - npm_cli_arg="$(cygpath -w "${npm_cli_js}")" - fi - loader_arg="$(cygpath -w "${loader_path}")" fi command -v "${node_cmd}" >/dev/null 2>&1 || { echo "node is required to run cross-OS release checks." >&2 exit 127 } -command -v "${npm_cmd}" >/dev/null 2>&1 || { - echo "npm is required to install the cross-OS release-check loader." >&2 - exit 127 -} -if [[ ! -f "${loader_path}" ]]; then - mkdir -p "${tool_dir}" - # The loader only needs tsx's packaged JS and esbuild platform package. - # Skipping lifecycle scripts avoids npm child-shell drift on Windows runners. - if [[ -n "${npm_cli_js}" ]]; then - if ! "${node_cmd}" "${npm_cli_arg}" install --prefix "${npm_tool_dir}" --no-save --no-package-lock --ignore-scripts "tsx@${tsx_version}" >/dev/null; then - echo "failed to install cross-OS release-check loader with ${node_cmd} ${npm_cli_arg}." >&2 - exit 127 - fi - elif ! "${npm_cmd}" install --prefix "${npm_tool_dir}" --no-save --no-package-lock --ignore-scripts "tsx@${tsx_version}" >/dev/null; then - echo "failed to install cross-OS release-check loader with ${npm_cmd}." >&2 - exit 127 - fi -fi - -if [[ ! -f "${loader_path}" ]]; then - echo "tsx loader missing after install: ${loader_path}" >&2 - find "${tool_dir}" -maxdepth 5 -type f 2>/dev/null | sort | sed 's/^/ /' >&2 || true - exit 127 -fi - -loader_url="$( - "${node_cmd}" -e ' - const { resolve } = require("node:path"); - const { pathToFileURL } = require("node:url"); - process.stdout.write(pathToFileURL(resolve(process.argv[1])).href); - ' "${loader_arg}" -)" - -exec "${node_cmd}" --import "${loader_url}" "${script_path}" "$@" +exec "${node_cmd}" "${script_path}" "$@" diff --git a/scripts/openclaw-cross-os-release-checks.ts b/scripts/openclaw-cross-os-release-checks.ts index 46413e161fc1..553ceaf3ec7a 100644 --- a/scripts/openclaw-cross-os-release-checks.ts +++ b/scripts/openclaw-cross-os-release-checks.ts @@ -1,6 +1,6 @@ -#!/usr/bin/env -S node --import tsx +#!/usr/bin/env node -// Executed directly via Node.js + tsx in the release workflow. +// Executed directly via Node.js native type stripping in the release workflow. import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index 9cc09ea46ec4..7b672c7eb7eb 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -503,6 +503,23 @@ describe("detectChangedScope", () => { runChangedSmoke: false, runControlUiI18n: false, }); + for (const releaseCheckPath of [ + ".github/workflows/openclaw-cross-os-release-checks-reusable.yml", + "scripts/github/run-openclaw-cross-os-release-checks.sh", + "scripts/openclaw-cross-os-release-checks.ts", + "test/scripts/openclaw-cross-os-release-workflow.test.ts", + ]) { + expect(detectChangedScope([releaseCheckPath]), releaseCheckPath).toEqual({ + runNode: true, + runMacos: false, + runIosBuild: false, + runAndroid: false, + runWindows: true, + runSkillsPython: false, + runChangedSmoke: false, + runControlUiI18n: false, + }); + } expect(detectChangedScope(["scripts/install.ps1"])).toEqual({ runNode: true, runMacos: false, diff --git a/test/scripts/openclaw-cross-os-release-workflow.test.ts b/test/scripts/openclaw-cross-os-release-workflow.test.ts index 643024190e2b..2a12539e4cae 100644 --- a/test/scripts/openclaw-cross-os-release-workflow.test.ts +++ b/test/scripts/openclaw-cross-os-release-workflow.test.ts @@ -1,10 +1,13 @@ // Openclaw Cross Os Release Workflow tests cover openclaw cross os release workflow script behavior. +import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; const WORKFLOW_PATH = ".github/workflows/openclaw-cross-os-release-checks-reusable.yml"; const WRAPPER_PATH = "scripts/github/run-openclaw-cross-os-release-checks.sh"; +const SCRIPT_PATH = "scripts/openclaw-cross-os-release-checks.ts"; const HARNESS = "bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh"; +const BASH_BIN = process.platform === "win32" ? "bash" : "/bin/bash"; describe("cross-OS release checks workflow", () => { it("runs the TypeScript release harness through the Windows-safe wrapper", () => { @@ -13,7 +16,7 @@ describe("cross-OS release checks workflow", () => { expect(workflow).toContain(HARNESS); expect(workflow).toContain("suite_filter:"); expect(workflow).toContain('--suite-filter "${INPUT_SUITE_FILTER}"'); - expect(workflow).not.toContain('pnpm dlx "tsx@${TSX_VERSION}"'); + expect(workflow).not.toContain("TSX_VERSION"); }); it("bounds npm baseline packing during prepare", () => { @@ -34,16 +37,61 @@ describe("cross-OS release checks workflow", () => { expect(workflow).toContain("process.stdout.write(`file_name=${fileName}\\n`);"); }); - it("uses Windows-safe npm resolution for the TypeScript loader bootstrap", () => { + it("executes the release harness directly with Node", () => { const wrapper = readFileSync(WRAPPER_PATH, "utf8"); + const script = readFileSync(SCRIPT_PATH, "utf8"); + const packageJson = JSON.parse(readFileSync("package.json", "utf8")) as { + scripts: Record; + }; - expect(wrapper).toContain("command -v npm.cmd"); - expect(wrapper).toContain('npm_tool_dir="$(cygpath -w "${tool_dir}")"'); - expect(wrapper).toContain('npm_cli_arg="$(cygpath -w "${npm_cli_js}")"'); - expect(wrapper).toContain('loader_arg="$(cygpath -w "${loader_path}")"'); - expect(wrapper).toContain('"${node_cmd}" "${npm_cli_arg}" install --prefix "${npm_tool_dir}"'); - expect(wrapper).toContain('"${npm_cmd}" install --prefix "${npm_tool_dir}"'); - expect(wrapper.match(/--ignore-scripts/g)).toHaveLength(2); - expect(wrapper).toContain('exec "${node_cmd}" --import "${loader_url}"'); + expect(wrapper).toContain('exec "${node_cmd}" "${script_path}" "$@"'); + expect(wrapper).not.toContain("npm"); + expect(wrapper).not.toContain("tsx"); + expect(wrapper).not.toContain("--import"); + expect(script).toMatch(/^#!\/usr\/bin\/env node$/mu); + expect(script).not.toContain("--import tsx"); + expect(packageJson.scripts["test:windows:ci"]).toContain( + "test/scripts/openclaw-cross-os-release-workflow.test.ts", + ); + + const result = spawnSync( + BASH_BIN, + [ + WRAPPER_PATH, + "--resolve-matrix", + "--ref", + "test/native-node", + "--mode", + "fresh", + "--suite-filter", + "windows/packaged-fresh", + "--windows-runner", + "windows-2025", + ], + { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + OPENCLAW_RELEASE_CHECKS_SCRIPT: SCRIPT_PATH, + }, + }, + ); + + expect(result.stderr).toBe(""); + expect(result.status).toBe(0); + expect(JSON.parse(result.stdout)).toEqual({ + include: [ + { + os_id: "windows", + display_name: "Windows", + runner: "windows-2025", + artifact_name: "windows", + suite: "packaged-fresh", + suite_label: "packaged fresh", + lane: "fresh", + }, + ], + }); }); });