mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 11:02:26 +00:00
fix(canvas): guard A2UI asset copy roots
This commit is contained in:
@@ -20,8 +20,27 @@ function shouldSkipMissingA2uiAssets(env = process.env) {
|
||||
return env.OPENCLAW_A2UI_SKIP_MISSING === "1" || Boolean(env.OPENCLAW_SPARSE_PROFILE);
|
||||
}
|
||||
|
||||
function isRelativeWithin(relPath) {
|
||||
return (
|
||||
relPath === "" ||
|
||||
(relPath !== ".." && !relPath.startsWith(`..${path.sep}`) && !path.isAbsolute(relPath))
|
||||
);
|
||||
}
|
||||
|
||||
function pathsOverlap(leftDir, rightDir) {
|
||||
const left = path.resolve(leftDir);
|
||||
const right = path.resolve(rightDir);
|
||||
return (
|
||||
isRelativeWithin(path.relative(left, right)) || isRelativeWithin(path.relative(right, left))
|
||||
);
|
||||
}
|
||||
|
||||
/** Copies A2UI assets, optionally tolerating missing bundles in sparse builds. */
|
||||
export async function copyA2uiAssets({ srcDir, outDir }) {
|
||||
if (pathsOverlap(srcDir, outDir)) {
|
||||
throw new Error("A2UI source and output directories must not overlap.");
|
||||
}
|
||||
|
||||
const skipMissing = shouldSkipMissingA2uiAssets(process.env);
|
||||
try {
|
||||
await fs.stat(path.join(srcDir, "index.html"));
|
||||
|
||||
@@ -37,9 +37,9 @@ describe("canvas a2ui copy", () => {
|
||||
|
||||
it("throws a helpful error when assets are missing", async () => {
|
||||
await withA2uiFixture(async (dir) => {
|
||||
await expect(copyA2uiAssets({ srcDir: dir, outDir: path.join(dir, "out") })).rejects.toThrow(
|
||||
'Run "pnpm canvas:a2ui:bundle"',
|
||||
);
|
||||
await expect(
|
||||
copyA2uiAssets({ srcDir: path.join(dir, "src"), outDir: path.join(dir, "out") }),
|
||||
).rejects.toThrow('Run "pnpm canvas:a2ui:bundle"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("canvas a2ui copy", () => {
|
||||
await withA2uiFixture(async (dir) => {
|
||||
process.env.OPENCLAW_A2UI_SKIP_MISSING = "1";
|
||||
await expect(
|
||||
copyA2uiAssets({ srcDir: dir, outDir: path.join(dir, "out") }),
|
||||
copyA2uiAssets({ srcDir: path.join(dir, "src"), outDir: path.join(dir, "out") }),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -56,7 +56,7 @@ describe("canvas a2ui copy", () => {
|
||||
await withA2uiFixture(async (dir) => {
|
||||
process.env.OPENCLAW_SPARSE_PROFILE = "core";
|
||||
await expect(
|
||||
copyA2uiAssets({ srcDir: dir, outDir: path.join(dir, "out") }),
|
||||
copyA2uiAssets({ srcDir: path.join(dir, "src"), outDir: path.join(dir, "out") }),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -102,4 +102,21 @@ describe("canvas a2ui copy", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects overlapping source and output directories before cleaning output", async () => {
|
||||
await withA2uiFixture(async (dir) => {
|
||||
const srcDir = path.join(dir, "src");
|
||||
await fs.mkdir(srcDir, { recursive: true });
|
||||
await fs.writeFile(path.join(srcDir, "index.html"), "<html></html>", "utf8");
|
||||
await fs.writeFile(path.join(srcDir, "a2ui.bundle.js"), "console.log(1);", "utf8");
|
||||
|
||||
await expect(copyA2uiAssets({ srcDir, outDir: srcDir })).rejects.toThrow("must not overlap");
|
||||
await expect(fs.readFile(path.join(srcDir, "index.html"), "utf8")).resolves.toBe(
|
||||
"<html></html>",
|
||||
);
|
||||
await expect(copyA2uiAssets({ srcDir, outDir: path.join(srcDir, "dist") })).rejects.toThrow(
|
||||
"must not overlap",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user