mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-06 13:36:53 +00:00
* perf(build): bound runtime artifact validation * test(build): consolidate gateway artifact build coverage Keep the three real bundler cases beside the artifact checker tests and reuse their temporary-root cleanup. This avoids an extra CI scheduling group while preserving emitted-byte, source-map and subset-build proof. Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * fix(build): include gateway metadata integration dependencies Copy the shared record helper into sparse declaration fixtures and use the canonical supported metadata version in release validation. Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
85 lines
3.4 KiB
TypeScript
85 lines
3.4 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import type { TsdownPlugin } from "tsdown";
|
|
import { isRecord } from "../../packages/normalization-core/src/record-coerce.ts";
|
|
|
|
export const GATEWAY_RUN_CHUNK_METADATA_VERSION = 1;
|
|
export const GATEWAY_RUN_CHUNK_METADATA_PATH = "cli/gateway-run-chunk.json";
|
|
|
|
function hashChunk(contents: string | Uint8Array): string {
|
|
return createHash("sha256").update(contents).digest("hex");
|
|
}
|
|
|
|
export function createGatewayRunChunkMetadataPlugin(rootDir = process.cwd()) {
|
|
const ownerModule = path
|
|
.resolve(rootDir, "src/cli/gateway-cli/run-command.ts")
|
|
.replaceAll("\\", "/");
|
|
return {
|
|
name: "openclaw:gateway-run-chunk-metadata",
|
|
generateBundle: {
|
|
order: "post",
|
|
handler(_options, bundle) {
|
|
// Module ownership survives chunk naming and minification; source-text markers do not.
|
|
const chunks = Object.values(bundle).flatMap((chunk) =>
|
|
chunk.type === "chunk" &&
|
|
chunk.moduleIds.some((id) => id.replaceAll("\\", "/") === ownerModule)
|
|
? [{ fileName: chunk.fileName, sha256: hashChunk(chunk.code) }]
|
|
: [],
|
|
);
|
|
// Subset builds need not include the CLI. The complete build guard requires this file.
|
|
if (chunks.length > 0) {
|
|
this.emitFile({
|
|
type: "asset",
|
|
fileName: GATEWAY_RUN_CHUNK_METADATA_PATH,
|
|
source: `${JSON.stringify({ version: GATEWAY_RUN_CHUNK_METADATA_VERSION, chunks })}\n`,
|
|
});
|
|
}
|
|
},
|
|
},
|
|
} satisfies TsdownPlugin;
|
|
}
|
|
|
|
export function readGatewayRunChunks(distDir: string, fsImpl: typeof fs = fs) {
|
|
const canonicalDistDir = fsImpl.realpathSync(distDir);
|
|
const metadataPath = path.join(distDir, GATEWAY_RUN_CHUNK_METADATA_PATH);
|
|
const metadataStat = fsImpl.lstatSync(metadataPath);
|
|
if (!metadataStat.isFile() || metadataStat.isSymbolicLink()) {
|
|
throw new Error("gateway run chunk metadata must be a regular file");
|
|
}
|
|
const metadata: unknown = JSON.parse(fsImpl.readFileSync(metadataPath, "utf8"));
|
|
if (
|
|
!isRecord(metadata) ||
|
|
metadata.version !== GATEWAY_RUN_CHUNK_METADATA_VERSION ||
|
|
!Array.isArray(metadata.chunks) ||
|
|
metadata.chunks.length === 0
|
|
) {
|
|
throw new Error("invalid gateway run chunk metadata");
|
|
}
|
|
return metadata.chunks.map((chunk: unknown) => {
|
|
if (
|
|
!isRecord(chunk) ||
|
|
typeof chunk.fileName !== "string" ||
|
|
typeof chunk.sha256 !== "string"
|
|
) {
|
|
throw new Error("invalid gateway run chunk metadata entry");
|
|
}
|
|
const filePath = path.resolve(distDir, chunk.fileName);
|
|
const relative = path.relative(distDir, filePath);
|
|
if (!relative || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
throw new Error("gateway run chunk must belong to the build output directory");
|
|
}
|
|
const stat = fsImpl.lstatSync(filePath);
|
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
throw new Error("gateway run chunk must be a regular file");
|
|
}
|
|
if (fsImpl.realpathSync(filePath) !== path.resolve(canonicalDistDir, relative)) {
|
|
throw new Error("gateway run chunk must not traverse a symbolic link");
|
|
}
|
|
const bytes = fsImpl.readFileSync(filePath);
|
|
if (hashChunk(bytes) !== chunk.sha256) {
|
|
throw new Error("gateway run chunk does not match its build metadata");
|
|
}
|
|
return { filePath, source: bytes.toString("utf8") };
|
|
});
|
|
}
|