mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
Some checks are pending
ClawSweeper Dispatch / dispatch (push) Waiting to run
CodeQL / Security High (actions) (push) Waiting to run
CodeQL / Security High (process-exec-boundary) (push) Waiting to run
Control UI Locale Refresh / resolve-base (push) Waiting to run
Control UI Locale Refresh / Verify generated PR App permissions (push) Blocked by required conditions
Control UI Locale Refresh / Refresh (push) Blocked by required conditions
Control UI Locale Refresh / Commit control UI locale refresh (push) Blocked by required conditions
Docs Sync Publish Repo / sync-publish-repo (push) Waiting to run
Docs / docs (push) Waiting to run
Native App Locale Refresh / Refresh native fr (push) Blocked by required conditions
Native App Locale Refresh / Refresh native hi (push) Blocked by required conditions
Native App Locale Refresh / Refresh native id (push) Blocked by required conditions
Plugin NPM Release / Seal prepared plugin npm release (push) Blocked by required conditions
Plugin NPM Release / Trusted publisher OIDC exchange (push) Blocked by required conditions
Plugin NPM Release / approve_plugins_npm_release (push) Blocked by required conditions
CodeQL / Security High (channel-runtime-boundary) (push) Waiting to run
CodeQL / Security High (core-auth-secrets) (push) Waiting to run
CodeQL / Security High (mcp-process-tool-boundary) (push) Waiting to run
CodeQL / Security High (network-ssrf-boundary) (push) Waiting to run
CodeQL / Security High (plugin-trust-boundary) (push) Waiting to run
Native App Locale Refresh / Refresh native pl (push) Blocked by required conditions
Native App Locale Refresh / Refresh native pt-BR (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ru (push) Blocked by required conditions
Native App Locale Refresh / Refresh native sv (push) Blocked by required conditions
Native App Locale Refresh / Refresh native th (push) Blocked by required conditions
Native App Locale Refresh / Refresh native tr (push) Blocked by required conditions
Native App Locale Refresh / Refresh native uk (push) Blocked by required conditions
Native App Locale Refresh / Refresh native vi (push) Blocked by required conditions
Native App Locale Refresh / Refresh native zh-CN (push) Blocked by required conditions
Native App Locale Refresh / Refresh native zh-TW (push) Blocked by required conditions
Native App Locale Refresh / Commit native locale refresh (push) Blocked by required conditions
Native App Locale Refresh / resolve-base (push) Waiting to run
Native App Locale Refresh / Verify generated PR App permissions (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ar (push) Blocked by required conditions
Native App Locale Refresh / Refresh native de (push) Blocked by required conditions
Native App Locale Refresh / Refresh native es (push) Blocked by required conditions
Native App Locale Refresh / Refresh native fa (push) Blocked by required conditions
Native App Locale Refresh / Refresh native it (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ja-JP (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ko (push) Blocked by required conditions
Native App Locale Refresh / Refresh native nl (push) Blocked by required conditions
Node Runtime Conformance / scope (push) Waiting to run
Node Runtime Conformance / TypeScript contracts (push) Blocked by required conditions
Node Runtime Conformance / Rust workspace (push) Blocked by required conditions
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Waiting to run
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Blocked by required conditions
Plugin Init Scaffold Validation / Validate provider scaffold (push) Blocked by required conditions
Plugin Init Scaffold Validation / scope (push) Waiting to run
Plugin NPM Release / preview_plugins_npm (push) Waiting to run
Plugin NPM Release / Validate release publish approval (push) Blocked by required conditions
Plugin NPM Release / preview_plugin_pack (push) Blocked by required conditions
Plugin NPM Release / Preflight plugin npm package () (push) Blocked by required conditions
Plugin NPM Release / Publish plugin npm package () (push) Blocked by required conditions
Vitest Cache Warm / dependencies (push) Waiting to run
Plugin NPM Release / verify_plugins_npm (push) Blocked by required conditions
Vitest Cache Warm / warm (push) Waiting to run
Workflow Sanity / actionlint (push) Waiting to run
Workflow Sanity / generated-doc-baselines (push) Waiting to run
materializeNativeCompiler copied the whole native typescript package into every fixture. 482 of its 493 files are dist/ (the typescript/unstable/* JavaScript API) and vendor/ (that API's JSON-RPC transport), which only toolchain identity hashing and fixture-resolved API imports read. Per consumer: - changed-lanes: root-test lint is boundary-free and parses config with the repository's own API, so the copy was never read; drop it. - write-plugin-sdk-entry-dts / write-unified-entry-dts: createFixture already copies the same package with its platform binary since the TypeScript 7 cutover; drop the redundant overlay. - run-tsgo-worktree, ownership --version preflight, Windows long-path resolution: launcher-only copies (javaScriptApi: false). - interceptor fixtures that exec a real compiler: exec the installed binary (resolveInstalledNativeCompiler); the lint-consumption case keeps its full fixture-local install and binary because preparation hashes it and admits default-library reads inside the checkout. - shard concurrency: its synthetic compiler needs no install. Build-artifact, extension-boundary, native-declaration, prepare/check boundary, and tsdown-config fixtures keep the full copy. Copies stay on the filtered close-on-exec cpSync path; assertions and timeouts are unchanged.
228 lines
9.1 KiB
TypeScript
228 lines
9.1 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { collectRuntimeImportClosure } from "../../scripts/lib/runtime-import-closure.mts";
|
|
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
|
import {
|
|
materializeNativeCompiler,
|
|
overrideNativeFixtureExecutable,
|
|
resolveInstalledNativeCompiler,
|
|
writeNativeFixtureFile,
|
|
} from "./native-boundary-fixture.js";
|
|
|
|
const roots = useAutoCleanupTempDirTracker(afterEach);
|
|
const sourceRoot = process.cwd();
|
|
// Commit only the wrapper's runtime closure: staging all of scripts/lib (~480
|
|
// files) exceeded the fixture's Git budget on loaded hosts. The CLI shim loads
|
|
// the implementation and its tsx loader by path, so they are explicit inputs.
|
|
const WRAPPER_FILES = collectRuntimeImportClosure(
|
|
sourceRoot,
|
|
["scripts/run-tsgo.mjs", "scripts/run-tsgo.mts", "scripts/tsx.mjs"],
|
|
{ includeDynamicImports: true },
|
|
);
|
|
|
|
function createLinkedCheckoutFixture() {
|
|
const directory = fs.realpathSync.native(roots.make("native-wrapper-worktree-"));
|
|
const primary = path.join(directory, "primary");
|
|
const root = path.join(directory, "linked");
|
|
fs.mkdirSync(primary);
|
|
// Isolate fixture Git writes from hook-owned indexes/objects. Wrapper commands
|
|
// below still inherit the untouched environment and exercise the real bootstrap.
|
|
const gitEnv = { ...process.env };
|
|
for (const key of [
|
|
"GIT_DIR",
|
|
"GIT_COMMON_DIR",
|
|
"GIT_WORK_TREE",
|
|
"GIT_INDEX_FILE",
|
|
"GIT_OBJECT_DIRECTORY",
|
|
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
"GIT_QUARANTINE_PATH",
|
|
]) {
|
|
delete gitEnv[key];
|
|
}
|
|
const git = (args: string[]) => {
|
|
const result = spawnSync(
|
|
"git",
|
|
[
|
|
`--git-dir=${path.join(primary, ".git")}`,
|
|
`--work-tree=${primary}`,
|
|
"-c",
|
|
`core.hooksPath=${path.join(directory, "unused-hooks")}`,
|
|
"-c",
|
|
"commit.gpgsign=false",
|
|
"-c",
|
|
"user.name=Fixture",
|
|
"-c",
|
|
"user.email=fixture@example.invalid",
|
|
...args,
|
|
],
|
|
{ cwd: primary, env: gitEnv, encoding: "utf8", timeout: 10_000 },
|
|
);
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.status, result.stdout + result.stderr).toBe(0);
|
|
};
|
|
git(["init", "-q"]);
|
|
writeNativeFixtureFile(primary, "package.json", '{"private":true,"type":"module"}\n');
|
|
writeNativeFixtureFile(primary, "pnpm-workspace.yaml", "packages: []\n");
|
|
writeNativeFixtureFile(primary, ".gitignore", "node_modules/\n.artifacts/\n");
|
|
for (const file of WRAPPER_FILES) {
|
|
const target = path.join(primary, file);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.copyFileSync(path.join(sourceRoot, file), target);
|
|
}
|
|
git(["add", "."]);
|
|
git(["commit", "-qm", "Synthetic compiler wrapper fixture"]);
|
|
git(["worktree", "add", "--detach", "-q", root]);
|
|
// An ancestor install must not let the worktree's own preload skip its Git fallback.
|
|
expect(path.relative(primary, root)).toBe(`..${path.sep}linked`);
|
|
expect(fs.lstatSync(path.join(root, ".git")).isFile()).toBe(true);
|
|
return { primary, root, git };
|
|
}
|
|
|
|
function linkCheckoutTools(root: string) {
|
|
for (const name of ["tsx", "@openclaw/fs-safe"]) {
|
|
const target = path.join(root, "node_modules", name);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.symlinkSync(path.join(sourceRoot, "node_modules", name), target, "junction");
|
|
}
|
|
}
|
|
|
|
function installCheckoutTools(root: string) {
|
|
const native = materializeNativeCompiler(root, { javaScriptApi: false });
|
|
linkCheckoutTools(root);
|
|
return native;
|
|
}
|
|
|
|
function runTsgoEntry(
|
|
root: string,
|
|
args: string[],
|
|
options: { cwd?: string; env?: NodeJS.ProcessEnv } = {},
|
|
) {
|
|
return spawnSync(process.execPath, [path.join(root, "scripts/run-tsgo.mjs"), ...args], {
|
|
cwd: options.cwd ?? root,
|
|
env: options.env ?? process.env,
|
|
encoding: "utf8",
|
|
timeout: 25_000,
|
|
killSignal: "SIGKILL",
|
|
});
|
|
}
|
|
|
|
describe("run-tsgo linked worktree entry", () => {
|
|
it("selects its own root compiler from src while preserving relative project semantics", () => {
|
|
const { primary, root } = createLinkedCheckoutFixture();
|
|
installCheckoutTools(primary);
|
|
linkCheckoutTools(root);
|
|
const native = resolveInstalledNativeCompiler();
|
|
const write = (file: string, text: string) => writeNativeFixtureFile(root, file, text);
|
|
const compilerOptions = {
|
|
module: "NodeNext",
|
|
target: "ES2023",
|
|
types: [],
|
|
strict: true,
|
|
noEmit: true,
|
|
};
|
|
write("tsconfig.json", JSON.stringify({ compilerOptions, files: ["wrong-entry.ts"] }));
|
|
write("wrong-entry.ts", 'export const value: number = "wrong project";\n');
|
|
write("src/tsconfig.json", JSON.stringify({ compilerOptions, files: ["entry.ts"] }));
|
|
write("src/entry.ts", "export const value: number = 1;\n");
|
|
const selectionPath = path.join(root, "compiler-selection.json");
|
|
// Record the selected executable and cwd, then let the unchanged native compiler
|
|
// parse -p and check the real project. No bootstrap or compiler result is mocked.
|
|
write(
|
|
"native-compiler.mjs",
|
|
`#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
fs.writeFileSync(${JSON.stringify(selectionPath)}, JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2) }));
|
|
const result = spawnSync(${JSON.stringify(native)}, process.argv.slice(2), { stdio: "inherit" });
|
|
if (result.error) throw result.error;
|
|
process.exitCode = result.status ?? 1;
|
|
`,
|
|
);
|
|
fs.chmodSync(path.join(root, "native-compiler.mjs"), 0o755);
|
|
const launcher = path.join(root, "node_modules/.bin/tsgo");
|
|
fs.mkdirSync(path.dirname(launcher), { recursive: true });
|
|
fs.symlinkSync("../../native-compiler.mjs", launcher, "file");
|
|
if (process.platform === "win32") {
|
|
write("node_modules/.bin/tsgo.cmd", '@node "%~dp0tsgo" %*\r\n');
|
|
}
|
|
overrideNativeFixtureExecutable(root, launcher);
|
|
const cwd = path.join(root, "src");
|
|
const result = runTsgoEntry(root, ["-p", "tsconfig.json"], {
|
|
cwd,
|
|
env: { ...process.env, OPENCLAW_CI_STATIC_EVIDENCE: "1" },
|
|
});
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.status, result.stdout + result.stderr).toBe(0);
|
|
const selected: { cwd: string; args: string[] } = JSON.parse(
|
|
fs.readFileSync(selectionPath, "utf8"),
|
|
);
|
|
expect(selected.cwd).toBe(cwd);
|
|
expect(selected.args.slice(0, 2)).toEqual(["-p", "tsconfig.json"]);
|
|
if (process.platform !== "win32") {
|
|
const receipts = result.stdout.trim().split("\n");
|
|
expect(receipts).toHaveLength(2);
|
|
const leaf = JSON.parse(receipts[0]!.slice(receipts[0]!.indexOf(" ") + 1));
|
|
expect(leaf).toEqual({
|
|
version: 1,
|
|
id: expect.any(String),
|
|
config: "tsconfig.json",
|
|
exitCode: 0,
|
|
stdout: "",
|
|
stderr: "",
|
|
});
|
|
expect(JSON.parse(receipts[1]!.slice(receipts[1]!.indexOf(" ") + 1))).toEqual({
|
|
version: 1,
|
|
id: expect.any(String),
|
|
planned: 1,
|
|
completed: 1,
|
|
leaves: [leaf.id],
|
|
});
|
|
}
|
|
expect(fs.lstatSync(path.join(cwd, "node_modules"), { throwIfNoEntry: false })).toBeUndefined();
|
|
}, 30_000);
|
|
|
|
it("refuses a missing local install through its own wrapper without creating dependency links", () => {
|
|
const { primary, root } = createLinkedCheckoutFixture();
|
|
installCheckoutTools(primary);
|
|
const installed = runTsgoEntry(primary, ["--version"]);
|
|
expect(installed.error).toBeUndefined();
|
|
expect(installed.status, installed.stdout + installed.stderr).toBe(0);
|
|
expect(installed.stdout).toMatch(/^Version \d/m);
|
|
const modules = path.join(root, "node_modules");
|
|
expect(fs.lstatSync(modules, { throwIfNoEntry: false })).toBeUndefined();
|
|
|
|
const result = runTsgoEntry(root, ["--version"]);
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.status, result.stdout + result.stderr).toBe(1);
|
|
expect(result.stdout).not.toMatch(/^Version \d/m);
|
|
expect(result.stderr.trim().split("\n").at(-1)).toBe("[tsgo] FAILED (exit 1)");
|
|
expect(fs.lstatSync(modules, { throwIfNoEntry: false })).toBeUndefined();
|
|
}, 30_000);
|
|
|
|
it("visibly skips a missing sparse project through its own wrapper before creating dependencies or outputs", () => {
|
|
const { primary, root, git } = createLinkedCheckoutFixture();
|
|
installCheckoutTools(primary);
|
|
git(["config", "core.sparseCheckout", "true"]);
|
|
const result = runTsgoEntry(
|
|
root,
|
|
[
|
|
"-p",
|
|
"test/tsconfig/tsconfig.core.test.json",
|
|
"--tsBuildInfoFile",
|
|
".artifacts/should-not-exist.tsbuildinfo",
|
|
],
|
|
{ env: { ...process.env, OPENCLAW_TSGO_SPARSE_SKIP: "1", OPENCLAW_CI_STATIC_EVIDENCE: "1" } },
|
|
);
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.status, result.stdout + result.stderr).toBe(0);
|
|
expect(result.stderr).toContain("skipping sparse-missing project");
|
|
expect(result.stderr).toContain("OPENCLAW_TSGO_SPARSE_SKIP=1");
|
|
expect(result.stdout).not.toContain("[ci-static:tsgo:");
|
|
expect(
|
|
fs.lstatSync(path.join(root, "node_modules"), { throwIfNoEntry: false }),
|
|
).toBeUndefined();
|
|
expect(fs.existsSync(path.join(root, ".artifacts"))).toBe(false);
|
|
}, 30_000);
|
|
});
|