mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
fix: preserve generated asset mtimes
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
type CopilotRuntimeBuild = (options: {
|
||||
bundle: boolean;
|
||||
entryPoints: string[];
|
||||
format: string;
|
||||
legalComments: string;
|
||||
minify: boolean;
|
||||
outfile: string;
|
||||
platform: string;
|
||||
target: string;
|
||||
tsconfig: string;
|
||||
write: false;
|
||||
}) => Promise<{
|
||||
outputFiles?: Array<{ text: string }>;
|
||||
}>;
|
||||
|
||||
/** Builds the Browser copilot runtime and returns whether the generated asset changed. */
|
||||
export function buildCopilotRuntime(params?: {
|
||||
build?: CopilotRuntimeBuild;
|
||||
outputPath?: string;
|
||||
}): Promise<boolean>;
|
||||
@@ -2,19 +2,38 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { build } from "esbuild";
|
||||
import { writeGeneratedTextAsset } from "../../../scripts/lib/generated-text-asset.mjs";
|
||||
|
||||
const pluginDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
const pluginDir = path.resolve(path.dirname(modulePath), "..");
|
||||
const repoRoot = path.resolve(pluginDir, "../..");
|
||||
const outfile = path.join(pluginDir, "chrome-extension", "modules", "copilot-runtime.js");
|
||||
|
||||
await build({
|
||||
entryPoints: [path.join(pluginDir, "scripts", "copilot-runtime-entry.ts")],
|
||||
outfile,
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
legalComments: "inline",
|
||||
minify: true,
|
||||
platform: "browser",
|
||||
target: "chrome125",
|
||||
tsconfig: path.join(repoRoot, "tsconfig.json"),
|
||||
});
|
||||
/** Builds the copilot runtime without rewriting an identical generated asset. */
|
||||
export async function buildCopilotRuntime(params = {}) {
|
||||
const buildImpl = params.build ?? build;
|
||||
const outputPath = params.outputPath ?? outfile;
|
||||
const result = await buildImpl({
|
||||
entryPoints: [path.join(pluginDir, "scripts", "copilot-runtime-entry.ts")],
|
||||
outfile: outputPath,
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
legalComments: "inline",
|
||||
minify: true,
|
||||
platform: "browser",
|
||||
target: "chrome125",
|
||||
tsconfig: path.join(repoRoot, "tsconfig.json"),
|
||||
write: false,
|
||||
});
|
||||
|
||||
const outputFile = result.outputFiles?.[0];
|
||||
if (!outputFile) {
|
||||
throw new Error("esbuild did not produce the Browser copilot runtime bundle");
|
||||
}
|
||||
|
||||
return writeGeneratedTextAsset(outputPath, outputFile.text);
|
||||
}
|
||||
|
||||
if (process.argv[1] === modulePath) {
|
||||
await buildCopilotRuntime();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { useAutoCleanupTempDirTracker } from "../test-support.js";
|
||||
import { buildCopilotRuntime } from "./build-copilot-runtime.mjs";
|
||||
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
|
||||
describe("scripts/build-copilot-runtime.mjs", () => {
|
||||
it("creates a missing bundle without rewriting it when unchanged", async () => {
|
||||
const rootDir = tempDirs.make("openclaw-browser-copilot-runtime-");
|
||||
const outputPath = path.join(rootDir, "copilot-runtime.js");
|
||||
const build = vi.fn(async () => ({
|
||||
outputFiles: [{ text: "export const copilotRuntime = true;\n" }],
|
||||
}));
|
||||
|
||||
await expect(buildCopilotRuntime({ build, outputPath })).resolves.toBe(true);
|
||||
expect(fs.readFileSync(outputPath, "utf8")).toBe("export const copilotRuntime = true;\n");
|
||||
|
||||
const initialTime = new Date("2026-07-18T04:00:00.000Z");
|
||||
fs.utimesSync(outputPath, initialTime, initialTime);
|
||||
|
||||
await expect(buildCopilotRuntime({ build, outputPath })).resolves.toBe(false);
|
||||
expect(fs.statSync(outputPath).mtimeMs).toBe(initialTime.getTime());
|
||||
expect(build).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
outfile: outputPath,
|
||||
write: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Builds browser runtime bundles for the diffs viewer assets.
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { build } from "esbuild";
|
||||
import { writeGeneratedTextAsset } from "./lib/generated-text-asset.mjs";
|
||||
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
const repoRoot = path.resolve(path.dirname(modulePath), "..");
|
||||
@@ -73,8 +73,6 @@ async function buildDiffsViewerRuntime(targetName) {
|
||||
}
|
||||
|
||||
const outputPath = path.join(repoRoot, target.output);
|
||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||
|
||||
const result = await build({
|
||||
entryPoints: [path.join(repoRoot, target.entry)],
|
||||
bundle: true,
|
||||
@@ -112,18 +110,7 @@ async function buildDiffsViewerRuntime(targetName) {
|
||||
}
|
||||
|
||||
const runtime = outputFile.text.replace(/[ \t]+$/gm, "");
|
||||
let previousRuntime = null;
|
||||
try {
|
||||
previousRuntime = await fs.readFile(outputPath, "utf8");
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousRuntime !== runtime) {
|
||||
await fs.writeFile(outputPath, runtime);
|
||||
}
|
||||
await writeGeneratedTextAsset(outputPath, runtime);
|
||||
}
|
||||
|
||||
if (process.argv[1] === modulePath) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { build } from "esbuild";
|
||||
import { writeGeneratedTextAsset } from "./lib/generated-text-asset.mjs";
|
||||
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
const repoRoot = path.resolve(path.dirname(modulePath), "..");
|
||||
@@ -14,7 +14,6 @@ const outputPath = path.join(repoRoot, "extensions/discord/assets/embedded-app-s
|
||||
export async function buildDiscordActivitySdk(params = {}) {
|
||||
const buildImpl = params.build ?? build;
|
||||
const targetPath = params.outputPath ?? outputPath;
|
||||
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
||||
const result = await buildImpl({
|
||||
entryPoints: ["@discord/embedded-app-sdk"],
|
||||
absWorkingDir: discordDir,
|
||||
@@ -33,21 +32,7 @@ export async function buildDiscordActivitySdk(params = {}) {
|
||||
throw new Error("esbuild did not produce the Discord Embedded App SDK bundle");
|
||||
}
|
||||
|
||||
const nextBundle = outputFile.text;
|
||||
let currentBundle = null;
|
||||
try {
|
||||
currentBundle = await fs.readFile(targetPath, "utf8");
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentBundle === nextBundle) {
|
||||
return false;
|
||||
}
|
||||
await fs.writeFile(targetPath, nextBundle);
|
||||
return true;
|
||||
return writeGeneratedTextAsset(targetPath, outputFile.text);
|
||||
}
|
||||
|
||||
if (process.argv[1] === modulePath) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
interface GeneratedTextAssetFs {
|
||||
readFile(path: string, encoding: "utf8"): Promise<string>;
|
||||
mkdir(path: string, options: { recursive: true }): Promise<unknown>;
|
||||
writeFile(path: string, contents: string, encoding: "utf8"): Promise<unknown>;
|
||||
}
|
||||
|
||||
/** Writes a generated text asset and returns whether its contents changed. */
|
||||
export function writeGeneratedTextAsset(
|
||||
filePath: string,
|
||||
contents: string,
|
||||
params?: { fs?: GeneratedTextAssetFs },
|
||||
): Promise<boolean>;
|
||||
@@ -0,0 +1,25 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* Writes a generated text asset only when its contents changed.
|
||||
*/
|
||||
export async function writeGeneratedTextAsset(filePath, contents, params = {}) {
|
||||
const fsImpl = params.fs ?? fs;
|
||||
let currentContents = null;
|
||||
try {
|
||||
currentContents = await fsImpl.readFile(filePath, "utf8");
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentContents === contents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await fsImpl.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fsImpl.writeFile(filePath, contents, "utf8");
|
||||
return true;
|
||||
}
|
||||
@@ -1443,6 +1443,14 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
|
||||
"scripts/lib/plugin-npm-runtime-assets.mjs",
|
||||
["test/scripts/plugin-npm-runtime-build-args.test.ts"],
|
||||
],
|
||||
[
|
||||
"scripts/lib/generated-text-asset.mjs",
|
||||
[
|
||||
"extensions/browser/scripts/build-copilot-runtime.test.ts",
|
||||
"test/scripts/build-diffs-viewer-runtime.test.ts",
|
||||
"test/scripts/bundled-plugin-assets.test.ts",
|
||||
],
|
||||
],
|
||||
[
|
||||
"scripts/lib/static-extension-assets.mjs",
|
||||
[
|
||||
@@ -2134,6 +2142,10 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
|
||||
["scripts/build-discord-activity-sdk.mjs", ["test/scripts/bundled-plugin-assets.test.ts"]],
|
||||
["scripts/build-diffs-viewer-runtime.mjs", ["test/scripts/build-diffs-viewer-runtime.test.ts"]],
|
||||
["scripts/run-node-watch-paths.mjs", ["test/scripts/bundled-plugin-assets.test.ts"]],
|
||||
[
|
||||
"extensions/browser/scripts/build-copilot-runtime.mjs",
|
||||
["extensions/browser/scripts/build-copilot-runtime.test.ts"],
|
||||
],
|
||||
["extensions/canvas/scripts/bundle-a2ui.mjs", ["extensions/canvas/scripts/bundle-a2ui.test.ts"]],
|
||||
["extensions/canvas/scripts/copy-a2ui.mjs", ["extensions/canvas/scripts/copy-a2ui.test.ts"]],
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user