fix(scripts): preserve symlinked API barrels

This commit is contained in:
Amp
2026-08-12 15:45:42 +00:00
committed by Peter Steinberger
parent 522f06ffc6
commit 54734a3f0c
4 changed files with 74 additions and 2 deletions
@@ -21,6 +21,17 @@ export type ExtensionWildcardReexportPolicy = {
remediationMessage: string;
};
async function isFileFollowingLinks(filePath: string): Promise<boolean> {
try {
return (await fs.stat(filePath)).isFile();
} catch (error) {
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
return false;
}
throw error;
}
}
async function listGuardedFiles(policy: ExtensionWildcardReexportPolicy) {
const files: string[] = [];
const recursive = policy.fileScope === "all-extension-api-files";
@@ -39,7 +50,12 @@ async function listGuardedFiles(policy: ExtensionWildcardReexportPolicy) {
await visit(filePath, depth + 1);
continue;
}
if ((recursive || depth === 1) && entry.isFile() && guardedFileNames.has(entry.name)) {
if (!guardedFileNames.has(entry.name) || (!recursive && depth !== 1)) {
continue;
}
// The root-only SDK guard historically follows API barrel symlinks; the
// recursive local-barrel guard intentionally retains Dirent semantics.
if (recursive ? entry.isFile() : await isFileFollowingLinks(filePath)) {
files.push(filePath);
}
}
@@ -1,6 +1,12 @@
// Check Plugin Sdk Wildcard Reexports tests cover check plugin sdk wildcard reexports script behavior.
import { describe, expect, it } from "vitest";
import { spawnSync } from "node:child_process";
import { copyFileSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { findPluginSdkWildcardReexports } from "../../scripts/check-plugin-sdk-wildcard-reexports.mts";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
describe("check-plugin-sdk-wildcard-reexports", () => {
it("flags wildcard re-exports from plugin-sdk subpaths", () => {
@@ -34,4 +40,50 @@ describe("check-plugin-sdk-wildcard-reexports", () => {
),
).toStrictEqual([]);
});
it("follows extension-root API barrel symlinks", () => {
const root = tempDirs.make("openclaw-plugin-sdk-wildcard-");
const scriptsDir = path.join(root, "scripts");
const scriptsLibDir = path.join(scriptsDir, "lib");
const extensionDir = path.join(root, "extensions", "fixture");
mkdirSync(scriptsLibDir, { recursive: true });
mkdirSync(extensionDir, { recursive: true });
writeFileSync(path.join(root, "package.json"), '{"type":"module"}\n');
writeFileSync(path.join(root, "pnpm-workspace.yaml"), "packages: []\n");
copyFileSync(
new URL("../../scripts/check-plugin-sdk-wildcard-reexports.mts", import.meta.url),
path.join(scriptsDir, "check-plugin-sdk-wildcard-reexports.mts"),
);
for (const fileName of ["extension-wildcard-reexport-scanner.mts", "repo-root.mjs"]) {
copyFileSync(
new URL(`../../scripts/lib/${fileName}`, import.meta.url),
path.join(scriptsLibDir, fileName),
);
}
writeFileSync(
path.join(extensionDir, "actual-api.ts"),
'export * from "openclaw/plugin-sdk/foo";\n',
);
symlinkSync("actual-api.ts", path.join(extensionDir, "api.ts"));
const result = spawnSync(
process.execPath,
[
"--import",
import.meta.resolve("tsx"),
path.join(scriptsDir, "check-plugin-sdk-wildcard-reexports.mts"),
"--json",
],
{ cwd: root, encoding: "utf8" },
);
expect(result.status).toBe(1);
expect(JSON.parse(result.stdout)).toEqual([
{
file: "extensions/fixture/api.ts",
line: 1,
text: 'export * from "openclaw/plugin-sdk/foo";',
},
]);
});
});
+3
View File
@@ -969,6 +969,7 @@ describe("scripts/test-projects changed-target routing", () => {
it.each([
"test/scripts/check-extension-package-tsc-boundary.test.ts",
"test/scripts/check-plugin-sdk-wildcard-reexports.test.ts",
"test/scripts/control-ui-i18n.test.ts",
])("routes process-group test %s to the isolated tooling shard", (testFile) => {
expectSingleVitestRunPlan(buildVitestRunPlans([testFile]), {
@@ -1370,6 +1371,7 @@ describe("scripts/test-projects changed-target routing", () => {
forwardedArgs: [],
includePatterns: [
"test/scripts/check-extension-package-tsc-boundary.test.ts",
"test/scripts/check-plugin-sdk-wildcard-reexports.test.ts",
"test/scripts/control-ui-i18n.test.ts",
"test/scripts/openclaw-e2e-instance.test.ts",
],
@@ -1556,6 +1558,7 @@ describe("scripts/test-projects changed-target routing", () => {
forwardedArgs: [],
includePatterns: [
"test/scripts/check-extension-package-tsc-boundary.test.ts",
"test/scripts/check-plugin-sdk-wildcard-reexports.test.ts",
"test/scripts/control-ui-i18n.test.ts",
"test/scripts/openclaw-e2e-instance.test.ts",
],
@@ -4,6 +4,7 @@ export const toolingIsolatedTestFiles = [
"test/plugins/bundled-provider-auth-literal-parity.2.test.ts",
"test/plugins/bundled-provider-auth-literal-parity.3.test.ts",
"test/scripts/check-extension-package-tsc-boundary.test.ts",
"test/scripts/check-plugin-sdk-wildcard-reexports.test.ts",
"test/scripts/control-ui-i18n.test.ts",
"test/scripts/openclaw-e2e-instance.test.ts",
];