mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
build: exclude private QA from npm package
This commit is contained in:
@@ -19,7 +19,6 @@ const PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS = BUNDLED_RUNTIME_SIDECAR_PATHS.fi
|
||||
);
|
||||
const LEGACY_UPDATE_COMPAT_RUNTIME_SIDECAR_PATHS = [
|
||||
"dist/extensions/qa-channel/runtime-api.js",
|
||||
"dist/extensions/qa-lab/runtime-api.js",
|
||||
] as const;
|
||||
const REQUIRED_INSTALLED_RUNTIME_SIDECAR_PATHS = [
|
||||
...PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS,
|
||||
@@ -316,7 +315,7 @@ describe("collectInstalledMirroredRootDependencyManifestErrors", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects private qa sidecar directories that are missing package.json", () => {
|
||||
it("rejects packaged qa channel sidecar directories that are missing package.json", () => {
|
||||
const packageRoot = makeInstalledPackageRoot();
|
||||
|
||||
try {
|
||||
@@ -325,21 +324,14 @@ describe("collectInstalledMirroredRootDependencyManifestErrors", () => {
|
||||
dependencies: {},
|
||||
});
|
||||
mkdirSync(join(packageRoot, "dist/extensions/qa-channel"), { recursive: true });
|
||||
mkdirSync(join(packageRoot, "dist/extensions/qa-lab"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(packageRoot, "dist/extensions/qa-channel/runtime-api.js"),
|
||||
"export {};\n",
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(
|
||||
join(packageRoot, "dist/extensions/qa-lab/runtime-api.js"),
|
||||
"export {};\n",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(collectInstalledMirroredRootDependencyManifestErrors(packageRoot)).toEqual([
|
||||
`installed bundled extension manifest missing: ${join(packageRoot, "dist/extensions/qa-channel/package.json")}.`,
|
||||
`installed bundled extension manifest missing: ${join(packageRoot, "dist/extensions/qa-lab/package.json")}.`,
|
||||
]);
|
||||
} finally {
|
||||
rmSync(packageRoot, { recursive: true, force: true });
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { WORKSPACE_TEMPLATE_PACK_PATHS } from "../scripts/lib/workspace-bootstrap-smoke.mjs";
|
||||
import {
|
||||
compareReleaseVersions,
|
||||
collectControlUiPackErrors,
|
||||
collectForbiddenPackedContentErrors,
|
||||
collectForbiddenPackedPathErrors,
|
||||
collectReleasePackageMetadataErrors,
|
||||
collectReleaseTagErrors,
|
||||
@@ -15,10 +19,13 @@ import {
|
||||
shouldSkipPackedTarballValidation,
|
||||
utcCalendarDayDistance,
|
||||
} from "../scripts/openclaw-npm-release-check.ts";
|
||||
import { PACKAGE_DIST_INVENTORY_RELATIVE_PATH } from "../src/infra/package-dist-inventory.ts";
|
||||
|
||||
const LEGACY_UPDATE_COMPAT_PACKED_PATHS = [
|
||||
"dist/extensions/qa-channel/runtime-api.js",
|
||||
"dist/extensions/qa-lab/runtime-api.js",
|
||||
const LEGACY_UPDATE_COMPAT_PACKED_PATHS = ["dist/extensions/qa-channel/runtime-api.js"] as const;
|
||||
const REQUIRED_PACKED_PATHS = [
|
||||
PACKAGE_DIST_INVENTORY_RELATIVE_PATH,
|
||||
...LEGACY_UPDATE_COMPAT_PACKED_PATHS,
|
||||
...WORKSPACE_TEMPLATE_PACK_PATHS,
|
||||
] as const;
|
||||
|
||||
describe("parseReleaseVersion", () => {
|
||||
@@ -286,11 +293,7 @@ describe("parseNpmPackJsonOutput", () => {
|
||||
describe("collectControlUiPackErrors", () => {
|
||||
it("rejects packs that ship the dashboard HTML without the asset payload", () => {
|
||||
expect(collectControlUiPackErrors(["dist/control-ui/index.html"])).toEqual([
|
||||
...LEGACY_UPDATE_COMPAT_PACKED_PATHS.map(
|
||||
(requiredPath) =>
|
||||
`npm package is missing required path "${requiredPath}". Ensure UI assets are built and included before publish.`,
|
||||
),
|
||||
...WORKSPACE_TEMPLATE_PACK_PATHS.map(
|
||||
...REQUIRED_PACKED_PATHS.map(
|
||||
(requiredPath) =>
|
||||
`npm package is missing required path "${requiredPath}". Ensure UI assets are built and included before publish.`,
|
||||
),
|
||||
@@ -302,8 +305,7 @@ describe("collectControlUiPackErrors", () => {
|
||||
expect(
|
||||
collectControlUiPackErrors([
|
||||
"dist/control-ui/index.html",
|
||||
...LEGACY_UPDATE_COMPAT_PACKED_PATHS,
|
||||
...WORKSPACE_TEMPLATE_PACK_PATHS,
|
||||
...REQUIRED_PACKED_PATHS,
|
||||
"dist/control-ui/assets/index-Bu8rSoJV.js",
|
||||
"dist/control-ui/assets/index-BK0yXA_h.css",
|
||||
]),
|
||||
@@ -332,20 +334,49 @@ describe("collectForbiddenPackedPathErrors", () => {
|
||||
"dist/extensions/qa-channel/package.json",
|
||||
"dist/extensions/qa-lab/runtime-api.js",
|
||||
"dist/extensions/qa-lab/src/cli.js",
|
||||
"dist/plugin-sdk/extensions/qa-lab/cli.d.ts",
|
||||
"dist/qa-runtime-B9LDtssJ.js",
|
||||
"qa/scenarios/index.md",
|
||||
]),
|
||||
).toEqual([
|
||||
'npm package must not include private QA channel artifact "dist/extensions/qa-channel/package.json".',
|
||||
'npm package must not include private QA lab artifact "dist/extensions/qa-lab/runtime-api.js".',
|
||||
'npm package must not include private QA lab artifact "dist/extensions/qa-lab/src/cli.js".',
|
||||
'npm package must not include private QA lab type artifact "dist/plugin-sdk/extensions/qa-lab/cli.d.ts".',
|
||||
'npm package must not include private QA runtime chunk "dist/qa-runtime-B9LDtssJ.js".',
|
||||
'npm package must not include private QA suite artifact "qa/scenarios/index.md".',
|
||||
]);
|
||||
});
|
||||
|
||||
it("allows the legacy update verifier QA runtime sidecars", () => {
|
||||
it("allows only the legacy update verifier QA channel runtime sidecar", () => {
|
||||
expect(
|
||||
collectForbiddenPackedPathErrors([
|
||||
"dist/extensions/qa-channel/runtime-api.js",
|
||||
"dist/extensions/qa-lab/runtime-api.js",
|
||||
]),
|
||||
).toEqual([]);
|
||||
).toEqual([
|
||||
'npm package must not include private QA lab artifact "dist/extensions/qa-lab/runtime-api.js".',
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects root dist chunks that still reference the private qa lab", () => {
|
||||
const rootDir = mkdtempSync(join(tmpdir(), "openclaw-pack-private-qa-"));
|
||||
|
||||
try {
|
||||
mkdirSync(join(rootDir, "dist"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(rootDir, "dist", "entry.js"),
|
||||
"//#region extensions/qa-lab/src/cli.ts\n",
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(join(rootDir, "README.md"), "developer docs mention extensions/qa-lab/\n");
|
||||
|
||||
expect(collectForbiddenPackedContentErrors(["dist/entry.js", "README.md"], rootDir)).toEqual([
|
||||
'npm package must not include private QA lab marker "//#region extensions/qa-lab/" in "dist/entry.js".',
|
||||
]);
|
||||
} finally {
|
||||
rmSync(rootDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,13 +9,14 @@ import {
|
||||
collectAppcastSparkleVersionErrors,
|
||||
collectBundledExtensionManifestErrors,
|
||||
collectBundledPluginRootRuntimeMirrorErrors,
|
||||
collectForbiddenPackContentPaths,
|
||||
collectRootDistBundledRuntimeMirrors,
|
||||
collectForbiddenPackPaths,
|
||||
collectMissingPackPaths,
|
||||
collectPackUnpackedSizeErrors,
|
||||
listRequiredQaScenarioPackPaths,
|
||||
packageNameFromSpecifier,
|
||||
} from "../scripts/release-check.ts";
|
||||
import { PACKAGE_DIST_INVENTORY_RELATIVE_PATH } from "../src/infra/package-dist-inventory.ts";
|
||||
import { bundledDistPluginFile, bundledPluginFile } from "./helpers/bundled-plugin-paths.js";
|
||||
|
||||
function makeItem(shortVersion: string, sparkleVersion: string): string {
|
||||
@@ -28,7 +29,6 @@ function makePackResult(filename: string, unpackedSize: number) {
|
||||
|
||||
const requiredPluginSdkPackPaths = [...listPluginSdkDistArtifacts(), "dist/plugin-sdk/compat.js"];
|
||||
const requiredBundledPluginPackPaths = listBundledPluginPackArtifacts();
|
||||
const requiredQaScenarioPackPaths = listRequiredQaScenarioPackPaths();
|
||||
|
||||
describe("collectAppcastSparkleVersionErrors", () => {
|
||||
it("accepts legacy 9-digit calver builds before lane-floor cutover", () => {
|
||||
@@ -309,6 +309,47 @@ describe("collectForbiddenPackPaths", () => {
|
||||
"dist/plugin-sdk/.tsbuildinfo",
|
||||
]);
|
||||
});
|
||||
|
||||
it("blocks private qa lab and suite paths from npm pack output", () => {
|
||||
expect(
|
||||
collectForbiddenPackPaths([
|
||||
"dist/index.js",
|
||||
"dist/extensions/qa-lab/runtime-api.js",
|
||||
"dist/plugin-sdk/extensions/qa-lab/cli.d.ts",
|
||||
"dist/plugin-sdk/qa-lab.js",
|
||||
"dist/plugin-sdk/qa-runtime.js",
|
||||
"dist/qa-runtime-B9LDtssJ.js",
|
||||
"qa/scenarios/index.md",
|
||||
]),
|
||||
).toEqual([
|
||||
"dist/extensions/qa-lab/runtime-api.js",
|
||||
"dist/plugin-sdk/extensions/qa-lab/cli.d.ts",
|
||||
"dist/plugin-sdk/qa-lab.js",
|
||||
"dist/plugin-sdk/qa-runtime.js",
|
||||
"dist/qa-runtime-B9LDtssJ.js",
|
||||
"qa/scenarios/index.md",
|
||||
]);
|
||||
});
|
||||
|
||||
it("blocks root dist chunks that still reference private qa lab sources", () => {
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "openclaw-release-private-qa-"));
|
||||
|
||||
try {
|
||||
mkdirSync(join(tempRoot, "dist"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(tempRoot, "dist", "entry.js"),
|
||||
"//#region extensions/qa-lab/src/runtime-api.ts\n",
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(join(tempRoot, "CHANGELOG.md"), "local QA notes mention extensions/qa-lab/\n");
|
||||
|
||||
expect(collectForbiddenPackContentPaths(["dist/entry.js", "CHANGELOG.md"], tempRoot)).toEqual(
|
||||
["dist/entry.js"],
|
||||
);
|
||||
} finally {
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectMissingPackPaths", () => {
|
||||
@@ -326,8 +367,8 @@ describe("collectMissingPackPaths", () => {
|
||||
expect(missing).toEqual(
|
||||
expect.arrayContaining([
|
||||
"dist/channel-catalog.json",
|
||||
PACKAGE_DIST_INVENTORY_RELATIVE_PATH,
|
||||
"dist/control-ui/index.html",
|
||||
"qa/scenarios/index.md",
|
||||
"scripts/npm-runner.mjs",
|
||||
"scripts/preinstall-package-manager-warning.mjs",
|
||||
"scripts/postinstall-bundled-plugins.mjs",
|
||||
@@ -343,9 +384,6 @@ describe("collectMissingPackPaths", () => {
|
||||
bundledDistPluginFile("whatsapp", "package.json"),
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
missing.some((path) => path.startsWith("qa/scenarios/") && path !== "qa/scenarios/index.md"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts the shipped upgrade surface when optional bundled metadata is present", () => {
|
||||
@@ -357,7 +395,6 @@ describe("collectMissingPackPaths", () => {
|
||||
"dist/extensions/acpx/mcp-proxy.mjs",
|
||||
bundledDistPluginFile("diffs", "assets/viewer-runtime.js"),
|
||||
...requiredBundledPluginPackPaths,
|
||||
...requiredQaScenarioPackPaths,
|
||||
...requiredPluginSdkPackPaths,
|
||||
...WORKSPACE_TEMPLATE_PACK_PATHS,
|
||||
"scripts/npm-runner.mjs",
|
||||
@@ -366,6 +403,7 @@ describe("collectMissingPackPaths", () => {
|
||||
"dist/plugin-sdk/root-alias.cjs",
|
||||
"dist/build-info.json",
|
||||
"dist/channel-catalog.json",
|
||||
PACKAGE_DIST_INVENTORY_RELATIVE_PATH,
|
||||
]),
|
||||
).toEqual([]);
|
||||
});
|
||||
@@ -381,15 +419,6 @@ describe("collectMissingPackPaths", () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("requires the authored qa scenario pack files in npm pack output", () => {
|
||||
expect(requiredQaScenarioPackPaths).toContain("qa/scenarios/index.md");
|
||||
expect(
|
||||
requiredQaScenarioPackPaths.some(
|
||||
(path) => path.startsWith("qa/scenarios/") && path !== "qa/scenarios/index.md",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectPackUnpackedSizeErrors", () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
pruneInstalledPackageDist,
|
||||
discoverBundledPluginRuntimeDeps,
|
||||
pruneBundledPluginSourceNodeModules,
|
||||
restoreLegacyUpdaterCompatSidecars,
|
||||
runBundledPluginPostinstall,
|
||||
} from "../../scripts/postinstall-bundled-plugins.mjs";
|
||||
import { writePackageDistInventory } from "../../src/infra/package-dist-inventory.ts";
|
||||
@@ -214,6 +215,63 @@ describe("bundled plugin postinstall", () => {
|
||||
await expect(fs.stat(staleFile)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
|
||||
it("restores only postinstall-generated QA lab compat sidecar after pruning old installs", async () => {
|
||||
const packageRoot = await createTempDirAsync("openclaw-packaged-install-qa-compat-");
|
||||
const currentFile = path.join(packageRoot, "dist", "entry.js");
|
||||
const stalePackage = path.join(packageRoot, "dist", "extensions", "qa-lab", "package.json");
|
||||
const staleManifest = path.join(
|
||||
packageRoot,
|
||||
"dist",
|
||||
"extensions",
|
||||
"qa-lab",
|
||||
"openclaw.plugin.json",
|
||||
);
|
||||
await fs.mkdir(path.dirname(stalePackage), { recursive: true });
|
||||
await fs.writeFile(currentFile, "export {};\n");
|
||||
await writePackageDistInventory(packageRoot);
|
||||
await fs.writeFile(stalePackage, "{}\n");
|
||||
await fs.writeFile(staleManifest, "{}\n");
|
||||
|
||||
runBundledPluginPostinstall({
|
||||
packageRoot,
|
||||
spawnSync: vi.fn(),
|
||||
log: { log: vi.fn(), warn: vi.fn() },
|
||||
});
|
||||
|
||||
await expect(fs.stat(stalePackage)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await expect(fs.stat(staleManifest)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await expect(
|
||||
fs.readFile(path.join(packageRoot, "dist", "extensions", "qa-lab", "runtime-api.js"), "utf8"),
|
||||
).resolves.toContain("QA Lab is not packaged");
|
||||
});
|
||||
|
||||
it("creates only an empty QA lab compat sidecar for fresh installs", async () => {
|
||||
const packageRoot = await createTempDirAsync("openclaw-packaged-install-no-qa-compat-");
|
||||
await fs.mkdir(path.join(packageRoot, "dist"), { recursive: true });
|
||||
await fs.writeFile(path.join(packageRoot, "dist", "entry.js"), "export {};\n");
|
||||
await writePackageDistInventory(packageRoot);
|
||||
|
||||
expect(
|
||||
restoreLegacyUpdaterCompatSidecars({
|
||||
packageRoot,
|
||||
removedFiles: ["dist/entry-old.js"],
|
||||
log: { log: vi.fn(), warn: vi.fn() },
|
||||
}),
|
||||
).toEqual(["dist/extensions/qa-lab/runtime-api.js"]);
|
||||
|
||||
await expect(
|
||||
fs.readFile(path.join(packageRoot, "dist", "extensions", "qa-lab", "runtime-api.js"), "utf8"),
|
||||
).resolves.toBe(
|
||||
"// Compatibility stub for older OpenClaw updaters. QA Lab is not packaged.\nexport {};\n",
|
||||
);
|
||||
await expect(
|
||||
fs.stat(path.join(packageRoot, "dist", "extensions", "qa-lab", "package.json")),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await expect(
|
||||
fs.stat(path.join(packageRoot, "dist", "extensions", "qa-lab", "openclaw.plugin.json")),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
|
||||
it("keeps packaged postinstall non-fatal when the dist inventory is missing", async () => {
|
||||
const packageRoot = await createTempDirAsync("openclaw-packaged-install-missing-inventory-");
|
||||
const staleFile = path.join(packageRoot, "dist", "channel-CJUAgRQR.js");
|
||||
|
||||
@@ -53,6 +53,13 @@ describe("test-install-sh-docker", () => {
|
||||
expect(script).toContain('from "./scripts/lib/npm-pack-budget.mjs"');
|
||||
expect(script).toContain("install smoke cannot verify pack budget");
|
||||
});
|
||||
|
||||
it("writes the package dist inventory before packing ignore-scripts tarballs", () => {
|
||||
const script = readFileSync(SCRIPT_PATH, "utf8");
|
||||
|
||||
expect(script).toContain("node --import tsx scripts/write-package-dist-inventory.ts");
|
||||
expect(script).toContain("quiet_npm pack --ignore-scripts");
|
||||
});
|
||||
});
|
||||
|
||||
describe("install-sh smoke runner", () => {
|
||||
|
||||
Reference in New Issue
Block a user