mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
fix(build): avoid duplicate asset copies during merge conflict resolution (#148907)
Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
This commit is contained in:
parent
4068854ba5
commit
aceff32680
3 changed files with 61 additions and 44 deletions
|
|
@ -23,7 +23,8 @@ function readJsonFile(filePath: string): unknown {
|
|||
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
function collectTrackedBundledPluginSourceCandidates(repoRoot: string) {
|
||||
/** Collect tracked metadata paths without reading plugin JSON or scanning directories. */
|
||||
export function collectTrackedBundledPluginSourceCandidates(repoRoot: string) {
|
||||
const pathspecs = [
|
||||
":(glob)extensions/*/openclaw.plugin.json",
|
||||
":(glob)extensions/*/package.json",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Discovers and copies static assets declared by bundled extension packages.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parseDockerSelectedPluginBuildIdFilter } from "./bundled-plugin-build-entries.mjs";
|
||||
import { collectTrackedBundledPluginSourceCandidates } from "./bundled-plugin-source-utils.mts";
|
||||
import { isRecord } from "./record-shared.mjs";
|
||||
|
||||
type StaticExtensionAsset = {
|
||||
|
|
@ -61,49 +61,12 @@ function listTrackedExtensionPackageDirs(rootDir: string, fsImpl: typeof fs) {
|
|||
if (fsImpl !== fs) {
|
||||
return null;
|
||||
}
|
||||
const result = spawnSync("git", ["ls-files", "--", ":(glob)extensions/*/package.json"], {
|
||||
cwd: rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
return null;
|
||||
}
|
||||
const deletedResult = spawnSync(
|
||||
"git",
|
||||
["ls-files", "--deleted", "--", ":(glob)extensions/*/package.json"],
|
||||
{
|
||||
cwd: rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
return collectTrackedBundledPluginSourceCandidates(rootDir)?.flatMap(
|
||||
({ dirName, pluginDir, packageJsonPath }) =>
|
||||
packageJsonPath
|
||||
? [{ dirName, hasPackageJson: true, packageDir: pluginDir, packageJsonPath }]
|
||||
: [],
|
||||
);
|
||||
if (deletedResult.status !== 0) {
|
||||
return null;
|
||||
}
|
||||
const deletedPaths = new Set(
|
||||
deletedResult.stdout.split("\n").map((line) => toPosixPath(line.trim())),
|
||||
);
|
||||
return result.stdout
|
||||
.split("\n")
|
||||
.map((line) => toPosixPath(line.trim()))
|
||||
.filter((line) => line.length > 0 && !deletedPaths.has(line))
|
||||
.flatMap((line) => {
|
||||
const match = /^extensions\/([^/]+)\/package\.json$/u.exec(line);
|
||||
if (!match?.[1]) {
|
||||
return [];
|
||||
}
|
||||
const packageDir = path.join(rootDir, "extensions", match[1]);
|
||||
return [
|
||||
{
|
||||
dirName: match[1],
|
||||
hasPackageJson: true,
|
||||
packageDir,
|
||||
packageJsonPath: path.join(packageDir, "package.json"),
|
||||
},
|
||||
];
|
||||
})
|
||||
.toSorted((left, right) => left.dirName.localeCompare(right.dirName));
|
||||
}
|
||||
|
||||
function listFilesystemExtensionPackageDirs(rootDir: string, fsImpl: typeof fs) {
|
||||
|
|
|
|||
|
|
@ -176,6 +176,59 @@ describe("runtime postbuild static assets", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("copies each package asset once with multiple Git index stages", async () => {
|
||||
const rootDir = createTempDir("openclaw-static-assets-index-");
|
||||
const git = (args: string[], input?: string) =>
|
||||
childProcess.execFileSync("git", args, { cwd: rootDir, encoding: "utf8", input });
|
||||
const pluginIds = ["conflicted", "package-only", "with-manifest"];
|
||||
for (const id of pluginIds) {
|
||||
const pluginDir = path.join(rootDir, "extensions", id);
|
||||
await fs.mkdir(pluginDir, { recursive: true });
|
||||
await fs.writeFile(path.join(pluginDir, "asset.txt"), `${id} bytes\n`);
|
||||
await fs.writeFile(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify({
|
||||
openclaw: { build: { staticAssets: [{ source: "asset.txt", output: "asset.txt" }] } },
|
||||
}),
|
||||
);
|
||||
}
|
||||
for (const id of ["with-manifest", "manifest-only"]) {
|
||||
const pluginDir = path.join(rootDir, "extensions", id);
|
||||
await fs.mkdir(pluginDir, { recursive: true });
|
||||
await fs.writeFile(path.join(pluginDir, "openclaw.plugin.json"), "not valid JSON");
|
||||
}
|
||||
git(["init", "-q"]);
|
||||
git(["add", "extensions"]);
|
||||
const packagePath = "extensions/conflicted/package.json";
|
||||
const blob = git(["hash-object", "-w", "--stdin"], "{}").trim();
|
||||
// Keep resolved working-tree metadata while the index still holds all merge stages.
|
||||
git(
|
||||
["update-index", "--index-info"],
|
||||
[
|
||||
`0 ${"0".repeat(blob.length)}\t${packagePath}`,
|
||||
...[1, 2, 3].map((stage) => `100644 ${blob} ${stage}\t${packagePath}`),
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
expect(git(["ls-files", "--", packagePath]).trim().split("\n")).toHaveLength(3);
|
||||
|
||||
const copy = vi.spyOn(fsSync, "copyFileSync");
|
||||
try {
|
||||
copyStaticExtensionAssets({ rootDir });
|
||||
expect(copy).toHaveBeenCalledTimes(3);
|
||||
expect(discoverStaticExtensionAssets({ rootDir }).map(({ pluginDir }) => pluginDir)).toEqual(
|
||||
pluginIds,
|
||||
);
|
||||
for (const id of pluginIds) {
|
||||
await expect(
|
||||
fs.readFile(path.join(rootDir, "dist", "extensions", id, "asset.txt"), "utf8"),
|
||||
).resolves.toBe(`${id} bytes\n`);
|
||||
}
|
||||
} finally {
|
||||
copy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ name: "top-level array", packageJson: [] },
|
||||
{ name: "array openclaw section", packageJson: { openclaw: [] } },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue