mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
fix(ci): support frozen script entrypoints (#121208)
* fix(ci): support frozen script entrypoints * fix(ci): route frozen plugin tests through package script * fix(release): support compiled candidate test helpers * fix(release): support compiled upgrade helpers * fix(release): mount trusted upgrade runtime * fix(release): preserve trusted tsx resolution
This commit is contained in:
parent
da0fad6718
commit
88fc335323
16 changed files with 272 additions and 54 deletions
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
|
|
@ -1339,7 +1339,7 @@ jobs:
|
|||
|
||||
if [ "$RUN_GATEWAY_WATCH" = "true" ] && [ "$PARALLEL_GATEWAY_WATCH" = "true" ]; then
|
||||
start_check "gateway-watch" \
|
||||
node --import tsx scripts/check-gateway-watch-regression.mts --skip-build
|
||||
pnpm test:gateway:watch-regression -- --skip-build
|
||||
fi
|
||||
|
||||
wait_checks
|
||||
|
|
@ -1348,7 +1348,7 @@ jobs:
|
|||
# starve the Gateway readiness deadline used by this regression gate.
|
||||
if [ "$RUN_GATEWAY_WATCH" = "true" ] && [ "$PARALLEL_GATEWAY_WATCH" != "true" ]; then
|
||||
start_check "gateway-watch" \
|
||||
node --import tsx scripts/check-gateway-watch-regression.mts --skip-build
|
||||
pnpm test:gateway:watch-regression -- --skip-build
|
||||
wait_checks
|
||||
fi
|
||||
|
||||
|
|
@ -1807,18 +1807,22 @@ jobs:
|
|||
run: |
|
||||
# Pack the public runtime before overlaying private QA artifacts so they cannot leak.
|
||||
unset OPENCLAW_BUILD_PRIVATE_QA
|
||||
node --import tsx scripts/build-all.mts qaRuntime
|
||||
pnpm build qaRuntime
|
||||
pnpm ui:build
|
||||
package_args=(
|
||||
--skip-build
|
||||
--output-dir .artifacts/qa-e2e/smoke-ci-package
|
||||
--output-name openclaw-current.tgz
|
||||
)
|
||||
if grep -Fq -- '--allow-unreleased-changelog' scripts/package-openclaw-for-docker.mts; then
|
||||
package_script="scripts/package-openclaw-for-docker.mts"
|
||||
if [[ ! -f "$package_script" ]]; then
|
||||
package_script="scripts/package-openclaw-for-docker.mjs"
|
||||
fi
|
||||
if grep -Fq -- '--allow-unreleased-changelog' "$package_script"; then
|
||||
package_args=(--allow-unreleased-changelog "${package_args[@]}")
|
||||
fi
|
||||
node scripts/package-openclaw-for-docker.mjs "${package_args[@]}"
|
||||
OPENCLAW_BUILD_PRIVATE_QA=1 node --import tsx scripts/build-all.mts qaRuntime
|
||||
OPENCLAW_BUILD_PRIVATE_QA=1 pnpm build qaRuntime
|
||||
|
||||
- name: Run smoke profile part
|
||||
env:
|
||||
|
|
@ -2638,7 +2642,11 @@ jobs:
|
|||
|
||||
case "$ADDITIONAL_CHECK_GROUP" in
|
||||
boundaries)
|
||||
node --import tsx scripts/run-additional-boundary-checks.mts
|
||||
boundary_runner=(node --import tsx scripts/run-additional-boundary-checks.mts)
|
||||
if [[ ! -f scripts/run-additional-boundary-checks.mts ]]; then
|
||||
boundary_runner=(node scripts/run-additional-boundary-checks.mjs)
|
||||
fi
|
||||
"${boundary_runner[@]}"
|
||||
;;
|
||||
prompt-snapshots)
|
||||
# No presence fallback: the boundary runner previously invoked
|
||||
|
|
@ -2662,25 +2670,19 @@ jobs:
|
|||
fi
|
||||
;;
|
||||
session-accessor-boundary)
|
||||
if [ ! -f scripts/check-session-accessor-boundary.mts ]; then
|
||||
echo "[skip] session accessor boundary check is not present in this checkout"
|
||||
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-accessor-boundary"] ? 0 : 1);'; then
|
||||
if ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-accessor-boundary"] ? 0 : 1);'; then
|
||||
echo "[skip] session accessor boundary script is not present in package.json"
|
||||
else
|
||||
run_check "lint:tmp:session-accessor-boundary" pnpm run lint:tmp:session-accessor-boundary
|
||||
fi
|
||||
if [ ! -f scripts/check-sqlite-transaction-boundary.mts ]; then
|
||||
echo "[skip] SQLite transaction boundary check is not present in this checkout"
|
||||
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:sqlite-transaction-boundary"] ? 0 : 1);'; then
|
||||
if ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:sqlite-transaction-boundary"] ? 0 : 1);'; then
|
||||
echo "[skip] SQLite transaction boundary script is not present in package.json"
|
||||
else
|
||||
run_check "lint:tmp:sqlite-transaction-boundary" pnpm run lint:tmp:sqlite-transaction-boundary
|
||||
fi
|
||||
;;
|
||||
session-transcript-reader-boundary)
|
||||
if [ ! -f scripts/check-session-transcript-reader-boundary.mts ]; then
|
||||
echo "[skip] session transcript reader boundary check is not present in this checkout"
|
||||
elif ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-transcript-reader-boundary"] ? 0 : 1);'; then
|
||||
if ! node -e 'const pkg = require("./package.json"); process.exit(pkg.scripts?.["lint:tmp:session-transcript-reader-boundary"] ? 0 : 1);'; then
|
||||
echo "[skip] session transcript reader boundary script is not present in package.json"
|
||||
else
|
||||
run_check "lint:tmp:session-transcript-reader-boundary" pnpm run lint:tmp:session-transcript-reader-boundary
|
||||
|
|
|
|||
2
.github/workflows/npm-telegram-beta-e2e.yml
vendored
2
.github/workflows/npm-telegram-beta-e2e.yml
vendored
|
|
@ -237,7 +237,7 @@ jobs:
|
|||
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node --import tsx scripts/build-all.mts qaRuntime
|
||||
pnpm build qaRuntime
|
||||
test -f dist/plugin-sdk/qa-runtime.js
|
||||
test -f dist/extensions/qa-lab/runtime-api.js
|
||||
|
||||
|
|
|
|||
12
.github/workflows/openclaw-release-checks.yml
vendored
12
.github/workflows/openclaw-release-checks.yml
vendored
|
|
@ -1253,7 +1253,7 @@ jobs:
|
|||
- name: Build private QA runtime
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||
run: pnpm build qaRuntime
|
||||
|
||||
- name: Run parity lane
|
||||
id: run_lane
|
||||
|
|
@ -1434,7 +1434,7 @@ jobs:
|
|||
- name: Build private QA runtime
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||
run: pnpm build qaRuntime
|
||||
|
||||
- name: Generate parity report
|
||||
id: generate_report
|
||||
|
|
@ -1517,7 +1517,7 @@ jobs:
|
|||
- name: Build private QA runtime
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||
run: pnpm build qaRuntime
|
||||
|
||||
- name: Run runtime-pair lane
|
||||
id: candidate_runtime_pair
|
||||
|
|
@ -2127,7 +2127,7 @@ jobs:
|
|||
- name: Build private QA runtime
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||
run: pnpm build qaRuntime
|
||||
|
||||
- name: Run Discord live lane
|
||||
id: run_lane
|
||||
|
|
@ -2226,7 +2226,7 @@ jobs:
|
|||
- name: Build private QA runtime
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||
run: pnpm build qaRuntime
|
||||
|
||||
- name: Run WhatsApp live lane
|
||||
id: run_lane
|
||||
|
|
@ -2322,7 +2322,7 @@ jobs:
|
|||
- name: Build private QA runtime
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||
run: pnpm build qaRuntime
|
||||
|
||||
- name: Run Slack live lane
|
||||
id: run_lane
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ jobs:
|
|||
OPENCLAW_BUILD_PRIVATE_QA=1 \
|
||||
PATH="$PATH" \
|
||||
RUNNER_TEMP="$RUNNER_TEMP" \
|
||||
pnpm exec node --import tsx scripts/build-all.mts qaRuntime
|
||||
pnpm build qaRuntime
|
||||
|
||||
- name: Move built candidate outside trusted workspace
|
||||
id: move_candidate
|
||||
|
|
@ -539,7 +539,7 @@ jobs:
|
|||
pnpm_version="$(pnpm --version)"
|
||||
jq -n \
|
||||
--arg archiveRoot "$archive_root" \
|
||||
--arg buildCommand "node --import tsx scripts/build-all.mts qaRuntime" \
|
||||
--arg buildCommand "pnpm build qaRuntime" \
|
||||
--arg candidateSha "$TARGET_SHA" \
|
||||
--arg candidateTree "$CANDIDATE_TREE" \
|
||||
--arg nodeVersion "$node_version" \
|
||||
|
|
@ -748,7 +748,7 @@ jobs:
|
|||
.candidateSha == $candidateSha and
|
||||
.candidateTree == $candidateTree and
|
||||
.archiveRoot == "openclaw-telegram-candidate" and
|
||||
.buildCommand == "node --import tsx scripts/build-all.mts qaRuntime" and
|
||||
.buildCommand == "pnpm build qaRuntime" and
|
||||
.sourceJob == "build_candidate"
|
||||
' "$manifest_path" >/dev/null
|
||||
[[ -d "$candidate_root/node_modules" && -f "$candidate_root/dist/index.js" ]]
|
||||
|
|
@ -899,7 +899,7 @@ jobs:
|
|||
id: build_harness
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node --import tsx scripts/build-all.mts qaRuntime
|
||||
run: pnpm build qaRuntime
|
||||
|
||||
- name: Validate candidate artifact metadata
|
||||
id: metadata
|
||||
|
|
|
|||
12
.github/workflows/plugin-prerelease.yml
vendored
12
.github/workflows/plugin-prerelease.yml
vendored
|
|
@ -346,14 +346,10 @@ jobs:
|
|||
childEnv.OPENCLAW_VITEST_INCLUDE_FILE = includeFile;
|
||||
}
|
||||
|
||||
const result = spawnSync(
|
||||
"pnpm",
|
||||
["exec", "node", "--import", "tsx", "scripts/test-projects.mts", ...configs],
|
||||
{
|
||||
env: childEnv,
|
||||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
const result = spawnSync("pnpm", ["test", "--", ...configs], {
|
||||
env: childEnv,
|
||||
stdio: "inherit",
|
||||
});
|
||||
process.exit(result.status ?? 1);
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -254,10 +254,27 @@ function relayChildStream(stream: Readable, label: string) {
|
|||
};
|
||||
}
|
||||
|
||||
export function resolveShardChildCommand(args: string[], nodeExecPath = process.execPath) {
|
||||
const TEST_PROJECTS_ENTRYPOINTS = ["scripts/test-projects.mts", "scripts/test-projects.mjs"];
|
||||
|
||||
export function resolveTestProjectsEntrypoint(
|
||||
fileExists: (path: string) => boolean = existsSync,
|
||||
): string {
|
||||
const entrypoint = TEST_PROJECTS_ENTRYPOINTS.find((candidate) => fileExists(candidate));
|
||||
if (!entrypoint) {
|
||||
throw new Error("CI target does not provide scripts/test-projects.mts or .mjs");
|
||||
}
|
||||
return entrypoint;
|
||||
}
|
||||
|
||||
export function resolveShardChildCommand(
|
||||
args: string[],
|
||||
nodeExecPath = process.execPath,
|
||||
testProjectsEntrypoint = resolveTestProjectsEntrypoint(),
|
||||
) {
|
||||
const loaderArgs = testProjectsEntrypoint.endsWith(".mts") ? ["--import", "tsx"] : [];
|
||||
return {
|
||||
command: nodeExecPath,
|
||||
args: ["--import", "tsx", "scripts/test-projects.mts", ...args],
|
||||
args: [...loaderArgs, testProjectsEntrypoint, ...args],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -812,7 +812,14 @@ seed_state() {
|
|||
}
|
||||
|
||||
apply_baseline_config_recipe() {
|
||||
node --import tsx scripts/e2e/lib/upgrade-survivor/config-recipe.mts apply \
|
||||
local tsx_import="${OPENCLAW_UPGRADE_SURVIVOR_TSX_IMPORT:-tsx}"
|
||||
local recipe_runner=(
|
||||
node --import "$tsx_import" scripts/e2e/lib/upgrade-survivor/config-recipe.mts
|
||||
)
|
||||
if [ ! -f scripts/e2e/lib/upgrade-survivor/config-recipe.mts ]; then
|
||||
recipe_runner=(node scripts/e2e/lib/upgrade-survivor/config-recipe.mjs)
|
||||
fi
|
||||
"${recipe_runner[@]}" apply \
|
||||
--summary "$CONFIG_COVERAGE_JSON" \
|
||||
--baseline-version "$baseline_version"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,12 @@ if [ "${OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE:-0}" = "1" ]; then
|
|||
fi
|
||||
|
||||
OPENCLAW_TEST_STATE_FUNCTION_B64="$(docker_e2e_test_state_function_b64)"
|
||||
TRUSTED_TSX_NODE_MODULES="$HARNESS_ROOT_DIR/node_modules"
|
||||
TRUSTED_TSX_IMPORT="$TRUSTED_TSX_NODE_MODULES/tsx/dist/loader.mjs"
|
||||
if [ ! -f "$TRUSTED_TSX_IMPORT" ]; then
|
||||
echo "Trusted upgrade-survivor tsx loader not found: $TRUSTED_TSX_IMPORT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" upgrade-survivor "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
||||
|
||||
|
|
@ -173,12 +179,14 @@ if [ "${OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE:-0}" = "1" ]; then
|
|||
-e OPENCLAW_UPGRADE_SURVIVOR_COMMAND_TIMEOUT="$COMMAND_TIMEOUT" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_LEGACY_RUNTIME_DEPS_SYMLINK="${OPENCLAW_UPGRADE_SURVIVOR_LEGACY_RUNTIME_DEPS_SYMLINK:-}" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS="$ROOT_MANAGED_VPS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_TSX_IMPORT=/tmp/openclaw-release-harness/node_modules/tsx/dist/loader.mjs \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_SUMMARY_JSON=/tmp/openclaw-upgrade-survivor-artifacts/summary.json \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS="$START_BUDGET_SECONDS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS="$STATUS_BUDGET_SECONDS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_CLAWHUB_FIXTURE_SERVER=/tmp/openclaw-clawhub-fixture-server.cjs \
|
||||
"${PROBE_ENV_ARGS[@]}" \
|
||||
-v "$ARTIFACT_DIR:/tmp/openclaw-upgrade-survivor-artifacts" \
|
||||
-v "$TRUSTED_TSX_NODE_MODULES:/tmp/openclaw-release-harness/node_modules:ro" \
|
||||
-v "$HARNESS_ROOT_DIR/scripts/e2e/lib/clawhub-fixture-server.cjs:/tmp/openclaw-clawhub-fixture-server.cjs:ro" \
|
||||
-v "$HARNESS_ROOT_DIR/scripts/e2e/lib/upgrade-survivor/run.sh:/tmp/openclaw-upgrade-survivor-run.sh:ro" \
|
||||
"${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]}" \
|
||||
|
|
|
|||
|
|
@ -150,10 +150,34 @@ docker_e2e_build_or_reuse() {
|
|||
return "$build_status"
|
||||
}
|
||||
|
||||
docker_e2e_test_state_entrypoint() {
|
||||
local extension entrypoint
|
||||
for extension in mts mjs; do
|
||||
entrypoint="$ROOT_DIR/scripts/lib/openclaw-test-state.$extension"
|
||||
if [ -f "$entrypoint" ]; then
|
||||
printf '%s\n' "$entrypoint"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "OpenClaw test-state entrypoint not found under $ROOT_DIR/scripts/lib" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
docker_e2e_run_test_state() {
|
||||
local entrypoint
|
||||
entrypoint="$(docker_e2e_test_state_entrypoint)" || return
|
||||
if [[ "$entrypoint" == *.mts ]]; then
|
||||
node --import tsx "$entrypoint" "$@"
|
||||
else
|
||||
node "$entrypoint" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
docker_e2e_test_state_shell_b64() {
|
||||
local label="${1:?missing test-state label}"
|
||||
local scenario="${2:-empty}"
|
||||
node --import tsx "$ROOT_DIR/scripts/lib/openclaw-test-state.mts" shell \
|
||||
docker_e2e_run_test_state shell \
|
||||
--label "$label" \
|
||||
--scenario "$scenario" |
|
||||
base64 |
|
||||
|
|
@ -161,7 +185,7 @@ docker_e2e_test_state_shell_b64() {
|
|||
}
|
||||
|
||||
docker_e2e_test_state_function_b64() {
|
||||
node --import tsx "$ROOT_DIR/scripts/lib/openclaw-test-state.mts" shell-function |
|
||||
docker_e2e_run_test_state shell-function |
|
||||
base64 |
|
||||
tr -d '\n'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
pruneFsModuleCache,
|
||||
resolveShardChildCommand,
|
||||
resolveShardPlans,
|
||||
resolveTestProjectsEntrypoint,
|
||||
runShardPlans,
|
||||
} from "../../scripts/ci-run-node-test-shard.mts";
|
||||
|
||||
|
|
@ -36,13 +37,28 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
describe("scripts/ci-run-node-test-shard.mts", () => {
|
||||
it("launches the child runner directly with Node", () => {
|
||||
it("launches the current TypeScript child runner directly with Node", () => {
|
||||
expect(resolveShardChildCommand(["one.config.ts"], "/runtime/node")).toEqual({
|
||||
command: "/runtime/node",
|
||||
args: ["--import", "tsx", "scripts/test-projects.mts", "one.config.ts"],
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the compiled child runner from a frozen candidate", () => {
|
||||
const entrypoint = resolveTestProjectsEntrypoint((candidate) => candidate.endsWith(".mjs"));
|
||||
expect(entrypoint).toBe("scripts/test-projects.mjs");
|
||||
expect(resolveShardChildCommand(["one.config.ts"], "/runtime/node", entrypoint)).toEqual({
|
||||
command: "/runtime/node",
|
||||
args: ["scripts/test-projects.mjs", "one.config.ts"],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails clearly when the candidate has no test-projects entrypoint", () => {
|
||||
expect(() => resolveTestProjectsEntrypoint(() => false)).toThrow(
|
||||
"CI target does not provide scripts/test-projects.mts or .mjs",
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers explicit targets and keeps one target per child", () => {
|
||||
const plans = resolveShardPlans({
|
||||
OPENCLAW_NODE_TEST_TARGETS_JSON: JSON.stringify(["a.test.ts", "b.test.ts"]),
|
||||
|
|
|
|||
|
|
@ -5679,6 +5679,37 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
|||
}
|
||||
});
|
||||
|
||||
it("uses candidate-owned script interfaces for frozen target CI", () => {
|
||||
const workflow = readCiWorkflow();
|
||||
const buildChecks = workflow.jobs["build-artifacts"].steps.find(
|
||||
(step: WorkflowStep) => step.name === "Run built artifact checks",
|
||||
);
|
||||
const qaBuild = workflow.jobs["qa-smoke-ci-profile"].steps.find(
|
||||
(step: WorkflowStep) => step.name === "Build QA smoke runtime",
|
||||
);
|
||||
const additionalChecks = workflow.jobs["check-additional-shard"].steps.find(
|
||||
(step: WorkflowStep) => step.name === "Run additional check shard",
|
||||
);
|
||||
|
||||
expect(buildChecks.run).toContain("pnpm test:gateway:watch-regression -- --skip-build");
|
||||
expect(buildChecks.run).not.toContain("scripts/check-gateway-watch-regression.mts");
|
||||
expect(qaBuild.run.match(/pnpm build qaRuntime/gu)).toHaveLength(2);
|
||||
expect(qaBuild.run).toContain('package_script="scripts/package-openclaw-for-docker.mts"');
|
||||
expect(qaBuild.run).toContain('package_script="scripts/package-openclaw-for-docker.mjs"');
|
||||
expect(additionalChecks.run).toContain(
|
||||
"boundary_runner=(node --import tsx scripts/run-additional-boundary-checks.mts)",
|
||||
);
|
||||
expect(additionalChecks.run).toContain(
|
||||
"boundary_runner=(node scripts/run-additional-boundary-checks.mjs)",
|
||||
);
|
||||
expect(additionalChecks.run).not.toContain(
|
||||
"if [ ! -f scripts/check-session-accessor-boundary.mts ]",
|
||||
);
|
||||
expect(additionalChecks.run).not.toContain(
|
||||
"if [ ! -f scripts/check-session-transcript-reader-boundary.mts ]",
|
||||
);
|
||||
});
|
||||
|
||||
it("emits one final CI gate after every selected lane", () => {
|
||||
const workflow = readCiWorkflow();
|
||||
const gate = workflow.jobs["ci-gate"];
|
||||
|
|
@ -6689,25 +6720,19 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
|||
expect(runStep.run).toContain("ci-routing)");
|
||||
expect(fastCoreJob["runs-on"]).toContain("matrix.runner");
|
||||
expect(smokeProfileJob.name).toBe("QA Smoke CI (${{ matrix.name }})");
|
||||
const publicRuntimeBuild = smokeBuildStep.run.indexOf(
|
||||
"node --import tsx scripts/build-all.mts qaRuntime",
|
||||
);
|
||||
const publicRuntimeBuild = smokeBuildStep.run.indexOf("pnpm build qaRuntime");
|
||||
const uiBuild = smokeBuildStep.run.indexOf("pnpm ui:build");
|
||||
const packageBuild = smokeBuildStep.run.indexOf("node scripts/package-openclaw-for-docker.mjs");
|
||||
const privateRuntimeBuild = smokeBuildStep.run.lastIndexOf(
|
||||
"node --import tsx scripts/build-all.mts qaRuntime",
|
||||
"OPENCLAW_BUILD_PRIVATE_QA=1 pnpm build qaRuntime",
|
||||
);
|
||||
expect(smokeBuildStep.run).toContain("node --import tsx scripts/build-all.mts qaRuntime");
|
||||
expect(smokeBuildStep.run).toContain("pnpm build qaRuntime");
|
||||
expect(smokeBuildStep.run).toContain("pnpm ui:build");
|
||||
expect(smokeBuildStep.env).not.toHaveProperty("OPENCLAW_BUILD_PRIVATE_QA");
|
||||
expect(smokeBuildStep.run).toContain("unset OPENCLAW_BUILD_PRIVATE_QA");
|
||||
expect(smokeBuildStep.run).toContain("--skip-build");
|
||||
expect(smokeBuildStep.run).toContain(
|
||||
"OPENCLAW_BUILD_PRIVATE_QA=1 node --import tsx scripts/build-all.mts qaRuntime",
|
||||
);
|
||||
expect(
|
||||
smokeBuildStep.run.match(/node --import tsx scripts\/build-all\.mts qaRuntime/g),
|
||||
).toHaveLength(2);
|
||||
expect(smokeBuildStep.run).toContain("OPENCLAW_BUILD_PRIVATE_QA=1 pnpm build qaRuntime");
|
||||
expect(smokeBuildStep.run.match(/pnpm build qaRuntime/g)).toHaveLength(2);
|
||||
expect(smokeBuildStep.run).toContain("--allow-unreleased-changelog");
|
||||
expect(smokeBuildStep.run).toContain("grep -Fq");
|
||||
expect(smokeBuildStep.run).toContain('"${package_args[@]}"');
|
||||
|
|
|
|||
|
|
@ -536,6 +536,38 @@ print_log_tail "$LOG_PATH"
|
|||
);
|
||||
});
|
||||
|
||||
it("resolves source and compiled candidate test-state entrypoints", () => {
|
||||
const resolveEntrypoint = (rootDir: string) =>
|
||||
spawnSync(
|
||||
"bash",
|
||||
["-c", `source "${DOCKER_E2E_IMAGE_HELPER_PATH}"; docker_e2e_test_state_entrypoint`],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, ROOT_DIR: rootDir },
|
||||
},
|
||||
);
|
||||
|
||||
const sourceResult = resolveEntrypoint(process.cwd());
|
||||
expect(sourceResult.status, sourceResult.stderr).toBe(0);
|
||||
expect(sourceResult.stdout.trim()).toBe(
|
||||
join(process.cwd(), "scripts/lib/openclaw-test-state.mts"),
|
||||
);
|
||||
|
||||
const compiledRoot = tempDirs.make("openclaw-compiled-test-state-");
|
||||
const missingResult = resolveEntrypoint(compiledRoot);
|
||||
expect(missingResult.status).toBe(1);
|
||||
expect(missingResult.stderr).toContain("OpenClaw test-state entrypoint not found");
|
||||
|
||||
const compiledDir = join(compiledRoot, "scripts/lib");
|
||||
mkdirSync(compiledDir, { recursive: true });
|
||||
const compiledEntrypoint = join(compiledDir, "openclaw-test-state.mjs");
|
||||
writeFileSync(compiledEntrypoint, "", "utf8");
|
||||
const compiledResult = resolveEntrypoint(compiledRoot);
|
||||
expect(compiledResult.status, compiledResult.stderr).toBe(0);
|
||||
expect(compiledResult.stdout.trim()).toBe(compiledEntrypoint);
|
||||
});
|
||||
|
||||
it("rejects malformed Docker E2E resource limits before a suite starts", () => {
|
||||
const helper = readFileSync(DOCKER_E2E_IMAGE_HELPER_PATH, "utf8");
|
||||
const scripts = [
|
||||
|
|
|
|||
|
|
@ -312,6 +312,22 @@ describe("release Telegram QA workflow", () => {
|
|||
});
|
||||
expect(step("trusted_identity", "Verify dispatched-main identity").id).toBe("identity");
|
||||
|
||||
const candidateBuild = requireRun(
|
||||
"build_candidate",
|
||||
"Build candidate runtime without runner credentials",
|
||||
);
|
||||
expect(candidateBuild).toContain("pnpm build qaRuntime");
|
||||
expect(candidateBuild).not.toContain("scripts/build-all.mts");
|
||||
expect(requireRun("build_candidate", "Archive bounded candidate tree")).toContain(
|
||||
'--arg buildCommand "pnpm build qaRuntime"',
|
||||
);
|
||||
expect(requireRun("attest_candidate", "Bounded extract and validate candidate")).toContain(
|
||||
'.buildCommand == "pnpm build qaRuntime"',
|
||||
);
|
||||
expect(requireRun("run_telegram", "Build trusted QA harness").trim()).toBe(
|
||||
"pnpm build qaRuntime",
|
||||
);
|
||||
|
||||
const runJob = job("run_telegram");
|
||||
expect(runJob.environment).toBe("qa-live-shared");
|
||||
expect(runJob["timeout-minutes"]).toBe(60);
|
||||
|
|
|
|||
|
|
@ -1768,7 +1768,7 @@ describe("package acceptance workflow", () => {
|
|||
OPENCLAW_BUILD_PRIVATE_QA: "1",
|
||||
});
|
||||
expectTextToIncludeAll(buildPrivateQa.run, [
|
||||
"node --import tsx scripts/build-all.mts qaRuntime",
|
||||
"pnpm build qaRuntime",
|
||||
"test -f dist/plugin-sdk/qa-runtime.js",
|
||||
"test -f dist/extensions/qa-lab/runtime-api.js",
|
||||
]);
|
||||
|
|
@ -1810,6 +1810,10 @@ describe("package acceptance workflow", () => {
|
|||
expect(releaseChecksWorkflow).toContain(
|
||||
'codex_plugin_spec="npm:@openclaw/codex@${BASH_REMATCH[1]}"',
|
||||
);
|
||||
expect(releaseChecksWorkflow.match(/run: pnpm build qaRuntime/gu)).toHaveLength(6);
|
||||
expect(releaseChecksWorkflow).not.toContain(
|
||||
"node --import tsx scripts/build-all.mts qaRuntime",
|
||||
);
|
||||
expect(releaseChecksWorkflow).toContain(
|
||||
"codex_plugin_spec: ${{ needs.resolve_target.outputs.codex_plugin_spec }}",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -465,6 +465,11 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
|||
expect(pluginManifestScript).toContain(
|
||||
"Plugin prerelease plan unavailable in target ref; skipping static and Docker plugin prerelease lanes.",
|
||||
);
|
||||
const pluginNodeShardScript = pluginWorkflow.jobs["plugin-prerelease-node-shard"].steps.find(
|
||||
(step: WorkflowStep) => step.name === "Run release-only plugin Node shard",
|
||||
).run;
|
||||
expect(pluginNodeShardScript).toContain('spawnSync("pnpm", ["test", "--", ...configs]');
|
||||
expect(pluginNodeShardScript).not.toContain("scripts/test-projects.mts");
|
||||
expect(pluginWorkflow.on.workflow_dispatch.inputs.target_ref).toEqual({
|
||||
default: "main",
|
||||
description: "Branch, tag, or full commit SHA to validate",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
// Upgrade Survivor Config Recipe tests cover upgrade survivor config recipe script behavior.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import {
|
||||
chmodSync,
|
||||
cpSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
|
@ -16,8 +24,66 @@ import {
|
|||
} from "../../scripts/e2e/lib/upgrade-survivor/config-recipe.mts";
|
||||
|
||||
const RECIPE_PATH = "scripts/e2e/lib/upgrade-survivor/config-recipe.mts";
|
||||
const RUN_PATH = "scripts/e2e/lib/upgrade-survivor/run.sh";
|
||||
const DOCKER_RUNNER_PATH = "scripts/e2e/upgrade-survivor-docker.sh";
|
||||
|
||||
describe("upgrade survivor config recipe command resolution", () => {
|
||||
it("uses trusted tsx or the candidate compiled config recipe entrypoint", () => {
|
||||
const runner = readFileSync(RUN_PATH, "utf8");
|
||||
const dockerRunner = readFileSync(DOCKER_RUNNER_PATH, "utf8");
|
||||
expect(runner).toContain("OPENCLAW_UPGRADE_SURVIVOR_TSX_IMPORT:-tsx");
|
||||
expect(runner).toContain(
|
||||
'node --import "$tsx_import" scripts/e2e/lib/upgrade-survivor/config-recipe.mts',
|
||||
);
|
||||
expect(runner).toContain(
|
||||
"recipe_runner=(node scripts/e2e/lib/upgrade-survivor/config-recipe.mjs)",
|
||||
);
|
||||
expect(runner).toContain('"${recipe_runner[@]}" apply');
|
||||
expect(dockerRunner).toContain(
|
||||
'TRUSTED_TSX_IMPORT="$TRUSTED_TSX_NODE_MODULES/tsx/dist/loader.mjs"',
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps trusted tsx dependencies resolvable at the Docker mount target", () => {
|
||||
const dockerRunner = readFileSync(DOCKER_RUNNER_PATH, "utf8");
|
||||
const loaderTarget = dockerRunner.match(
|
||||
/OPENCLAW_UPGRADE_SURVIVOR_TSX_IMPORT=(\/tmp\/\S+\/loader\.mjs)/u,
|
||||
)?.[1];
|
||||
const mountTarget = dockerRunner.match(
|
||||
/-v "\$TRUSTED_TSX_NODE_MODULES:(\/tmp\/[^:"]+):ro"/u,
|
||||
)?.[1];
|
||||
expect(loaderTarget).toBeTruthy();
|
||||
expect(mountTarget).toBeTruthy();
|
||||
if (!loaderTarget || !mountTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-upgrade-tsx-mount-"));
|
||||
try {
|
||||
const mountedNodeModules = join(root, mountTarget.slice(1));
|
||||
mkdirSync(mountedNodeModules, { recursive: true });
|
||||
for (const packageName of ["tsx", "esbuild", "@esbuild"]) {
|
||||
cpSync(
|
||||
join(process.cwd(), "node_modules", packageName),
|
||||
join(mountedNodeModules, packageName),
|
||||
{
|
||||
dereference: true,
|
||||
recursive: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const loaderPath = join(root, loaderTarget.slice(1));
|
||||
const result = spawnSync(process.execPath, ["--import", loaderPath, "-e", ""], {
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("compares baseline versions with the shared release parser", () => {
|
||||
expect(isReleaseBefore("2026.3.31", "2026.4.0")).toBe(true);
|
||||
expect(isReleaseBefore("2026.3.31-beta.1", "2026.4.0")).toBe(true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue