mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: consolidate remaining coercion helpers (#122020)
This commit is contained in:
committed by
GitHub
parent
24bbb416b5
commit
cad77fb39c
@@ -472,7 +472,7 @@ describe("package-openclaw-for-docker", () => {
|
||||
const packageJsonPath = path.join(sourceDir, "package.json");
|
||||
const originalPackageJson = `${JSON.stringify(
|
||||
{
|
||||
dependencies: { "@openclaw/ai": "workspace:*", "dep-a": "1.2.3" },
|
||||
dependencies: { "@openclaw/ai": "workspace:*", "dep-a": "workspace:1.2.3" },
|
||||
devDependencies: { "@openclaw/session-url-contract": "workspace:*" },
|
||||
files: ["dist"],
|
||||
name: "openclaw",
|
||||
@@ -506,7 +506,10 @@ describe("package-openclaw-for-docker", () => {
|
||||
fs.writeFileSync(
|
||||
path.join(destination, "package.json"),
|
||||
`${JSON.stringify({
|
||||
dependencies: { "dep-a": "1.2.3" },
|
||||
dependencies: {
|
||||
"@openclaw/private-runtime": "0.0.0-private",
|
||||
"dep-a": "1.2.3",
|
||||
},
|
||||
name: "@openclaw/ai",
|
||||
version: "2026.6.17",
|
||||
})}\n`,
|
||||
@@ -522,6 +525,8 @@ describe("package-openclaw-for-docker", () => {
|
||||
devDependencies?: Record<string, string>;
|
||||
};
|
||||
expect(packageJson.dependencies["@openclaw/ai"]).toBe("2026.6.17");
|
||||
expect(packageJson.dependencies["@openclaw/private-runtime"]).toBeUndefined();
|
||||
expect(packageJson.dependencies["dep-a"]).toBe("1.2.3");
|
||||
expect(packageJson.devDependencies?.["@openclaw/session-url-contract"]).toBe("workspace:*");
|
||||
expect(packageJson.bundleDependencies).toContain("@openclaw/ai");
|
||||
expect(fs.existsSync(path.join(installedAiPath, "original-marker"))).toBe(false);
|
||||
|
||||
@@ -15,6 +15,7 @@ const ZERO_INSTALL_FILES = [
|
||||
"scripts/android-release-signing.mjs",
|
||||
"scripts/lib/android-release-signing-process.mjs",
|
||||
"scripts/lib/arg-utils.runtime.mjs",
|
||||
"scripts/lib/record-shared.mjs",
|
||||
"scripts/lib/repo-root.mjs",
|
||||
"apps/android/Config/ReleaseSigning.json",
|
||||
] as const;
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
auditCoercionHelperDeclarations,
|
||||
findBannedCoercionHelperDeclarations,
|
||||
isGovernedCoercionHelperPath,
|
||||
runCoercionHelperDeclarationGuard,
|
||||
type CoercionHelperCarveOut,
|
||||
type CoercionHelperDeclaration,
|
||||
} from "../../scripts/check-coercion-helper-declarations.mts";
|
||||
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
||||
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
|
||||
describe("coercion helper declaration AST guard", () => {
|
||||
it("finds nested, exported, async, and callable-variable declarations", () => {
|
||||
it("finds functions, callable variables, methods, fields, and object properties", () => {
|
||||
const source = [
|
||||
"export async function readString() {}",
|
||||
"if (true) {",
|
||||
@@ -28,6 +34,18 @@ describe("coercion helper declaration AST guard", () => {
|
||||
"const readOptionalString = normalizeOptionalString;",
|
||||
"const optionalString = helpers.readStringValue;",
|
||||
"const asObject = helpers.asOptionalRecord;",
|
||||
"class Example {",
|
||||
" readString() {}",
|
||||
" toError = () => new Error();",
|
||||
" asRecord = function () { return {}; };",
|
||||
"}",
|
||||
"const object = {",
|
||||
" optionalString() {},",
|
||||
" readBoolean: () => true,",
|
||||
" readNumber: function () { return 1; },",
|
||||
"};",
|
||||
"function normalizeOptionalString() {}",
|
||||
"const parseDateFirstTimestampMs = () => 0;",
|
||||
].join("\n");
|
||||
|
||||
expect(findBannedCoercionHelperDeclarations(source, "src/example.ts")).toEqual([
|
||||
@@ -44,19 +62,42 @@ describe("coercion helper declaration AST guard", () => {
|
||||
{ file: "src/example.ts", kind: "function", line: 13, name: "normalizeString" },
|
||||
{ file: "src/example.ts", kind: "variable", line: 14, name: "asString" },
|
||||
{ file: "src/example.ts", kind: "function", line: 15, name: "asObject" },
|
||||
{ file: "src/example.ts", kind: "variable", line: 16, name: "readOptionalString" },
|
||||
{
|
||||
file: "src/example.ts",
|
||||
kind: "variable",
|
||||
line: 16,
|
||||
name: "readOptionalString",
|
||||
},
|
||||
{ file: "src/example.ts", kind: "variable", line: 17, name: "optionalString" },
|
||||
{ file: "src/example.ts", kind: "variable", line: 18, name: "asObject" },
|
||||
{ file: "src/example.ts", kind: "method", line: 20, name: "readString" },
|
||||
{ file: "src/example.ts", kind: "field", line: 21, name: "toError" },
|
||||
{ file: "src/example.ts", kind: "field", line: 22, name: "asRecord" },
|
||||
{ file: "src/example.ts", kind: "method", line: 25, name: "optionalString" },
|
||||
{ file: "src/example.ts", kind: "property", line: 26, name: "readBoolean" },
|
||||
{ file: "src/example.ts", kind: "property", line: 27, name: "readNumber" },
|
||||
{
|
||||
file: "src/example.ts",
|
||||
kind: "function",
|
||||
line: 29,
|
||||
name: "normalizeOptionalString",
|
||||
},
|
||||
{
|
||||
file: "src/example.ts",
|
||||
kind: "variable",
|
||||
line: 30,
|
||||
name: "parseDateFirstTimestampMs",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores imports, aliases, methods, properties, callbacks, and inert text", () => {
|
||||
it("ignores imports, non-callable properties, shorthand aliases, callback names, and inert text", () => {
|
||||
const source = [
|
||||
'import { isRecord, readString as importedReadString } from "./helpers.js";',
|
||||
"const alias = isRecord;",
|
||||
"const { asRecord } = helpers;",
|
||||
"class Example { isRecord() {} readString = () => true; }",
|
||||
"const object = { optionalString() {}, toError: () => new Error() };",
|
||||
"const object = { isRecord, readString: 42, toError: importedToError };",
|
||||
"const shorthand = { optionalString };",
|
||||
"values.map(function readString(value) { return value; });",
|
||||
"const aliasWithInternalName = function isRecord(value) { return value; };",
|
||||
"const asRecord = raw as Record<string, unknown>;",
|
||||
@@ -112,20 +153,61 @@ describe("coercion helper declaration AST guard", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("excludes declarations, fixtures, generated sources, and browser bundles", () => {
|
||||
it("excludes structural fixtures and generated sources without hiding authored fixture-named files", () => {
|
||||
expect(isGovernedCoercionHelperPath("src/runtime.ts")).toBe(true);
|
||||
expect(isGovernedCoercionHelperPath("extensions/demo/runtime.jsx")).toBe(true);
|
||||
expect(isGovernedCoercionHelperPath("src/runtime.d.ts")).toBe(false);
|
||||
expect(isGovernedCoercionHelperPath("scripts/runtime.d.mts")).toBe(false);
|
||||
expect(isGovernedCoercionHelperPath("test/fixtures/example.ts")).toBe(false);
|
||||
expect(isGovernedCoercionHelperPath("extensions/demo/dist/index.js")).toBe(false);
|
||||
expect(isGovernedCoercionHelperPath("src/example.test-fixtures.ts")).toBe(false);
|
||||
expect(isGovernedCoercionHelperPath("src/example.test-fixtures.ts")).toBe(true);
|
||||
expect(isGovernedCoercionHelperPath("extensions/demo/runtime-tool-fixture.ts")).toBe(true);
|
||||
expect(isGovernedCoercionHelperPath("src/schema.generated.ts")).toBe(false);
|
||||
expect(isGovernedCoercionHelperPath("ui/src/vendor.bundle.js")).toBe(false);
|
||||
expect(
|
||||
isGovernedCoercionHelperPath(
|
||||
"extensions/browser/chrome-extension/modules/copilot-runtime.js",
|
||||
),
|
||||
).toBe(false);
|
||||
).toBe(true);
|
||||
expect(isGovernedCoercionHelperPath("root.config.ts")).toBe(true);
|
||||
expect(isGovernedCoercionHelperPath(".github/actions/example/index.ts")).toBe(true);
|
||||
});
|
||||
|
||||
it("scans a temporary repository and reports sorted, owner-specific diagnostics", () => {
|
||||
const repoRoot = tempDirs.make("coercion-helper-guard-");
|
||||
fs.mkdirSync(path.join(repoRoot, "src"), { recursive: true });
|
||||
fs.mkdirSync(path.join(repoRoot, "extensions", "demo"), { recursive: true });
|
||||
fs.mkdirSync(path.join(repoRoot, "config"), { recursive: true });
|
||||
fs.writeFileSync(path.join(repoRoot, "src", "z.ts"), "function readString() {}\n");
|
||||
fs.writeFileSync(
|
||||
path.join(repoRoot, "extensions", "demo", "a.ts"),
|
||||
"const asRecord = () => ({});\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(repoRoot, "config", "root.ts"),
|
||||
"class Config { normalizeOptionalString() {} }\n",
|
||||
);
|
||||
const stdout: string[] = [];
|
||||
const stderr: string[] = [];
|
||||
expect(
|
||||
runCoercionHelperDeclarationGuard({
|
||||
carveOuts: [],
|
||||
repoRoot,
|
||||
io: {
|
||||
stdout: { write: (value) => stdout.push(value) },
|
||||
stderr: { write: (value) => stderr.push(value) },
|
||||
},
|
||||
}),
|
||||
).toBe(1);
|
||||
|
||||
expect(stdout).toEqual([]);
|
||||
const output = stderr.join("");
|
||||
expect(output.indexOf("config/root.ts:1")).toBeLessThan(
|
||||
output.indexOf("extensions/demo/a.ts:1"),
|
||||
);
|
||||
expect(output.indexOf("extensions/demo/a.ts:1")).toBeLessThan(output.indexOf("src/z.ts:1"));
|
||||
expect(output).toContain("Core/package/UI/workspace-script code");
|
||||
expect(output).toContain("Plugin production code");
|
||||
expect(output).toContain("Dependency-free, copied, generated, or serialized code");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5003,6 +5003,8 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
"matrix.task == 'max-lines-ratchet' && github.event_name == 'workflow_dispatch' && inputs.release_gate",
|
||||
);
|
||||
expect(checksFastRun.run).toContain("max-lines-ratchet)");
|
||||
expect(checksFastRun.run).toContain("coercion-helpers)");
|
||||
expect(checksFastRun.run).toContain("pnpm check:coercion-helpers");
|
||||
expect(checksFastRun.run).toContain('has_package_script "check:max-lines-ratchet"');
|
||||
expect(checksFastRun.env.RATCHET_PR_HEAD_SHA).toBe(
|
||||
"${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}",
|
||||
@@ -5094,6 +5096,11 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
runtime: "node",
|
||||
task: "max-lines-ratchet",
|
||||
},
|
||||
{
|
||||
check_name: "checks-fast-coercion-helpers",
|
||||
runtime: "node",
|
||||
task: "coercion-helpers",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { toErrorObject as toLintErrorObject } from "@openclaw/normalization-core/error-coercion";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectProdResolvedPackagesFromLockfile,
|
||||
@@ -485,17 +486,3 @@ snapshots:
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// PR Context And Evidence Policy tests cover GitHub PR-body policy behavior.
|
||||
import { readFileSync } from "node:fs";
|
||||
import { toErrorObject as toLintErrorObject } from "@openclaw/normalization-core/error-coercion";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
NEEDS_PR_CONTEXT_LABEL,
|
||||
@@ -689,17 +690,3 @@ describe("readBoundedGitHubApiJson", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promise
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { toErrorObject as toLintErrorObject } from "@openclaw/normalization-core/error-coercion";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveWindowsTaskkillPath } from "../../scripts/lib/windows-taskkill.mjs";
|
||||
import {
|
||||
@@ -1433,17 +1434,3 @@ describe("resolve-openclaw-package-candidate", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
|
||||
if (value instanceof Error) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return new Error(value);
|
||||
}
|
||||
const error = new Error(fallbackMessage, { cause: value });
|
||||
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
||||
Object.assign(error, value);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user