fix(crabbox): keep packaged commands runnable without dev dependencies (#125419)

This commit is contained in:
Peter Steinberger
2026-08-25 20:18:44 -07:00
committed by GitHub
parent 8954634b15
commit 570072f090
5 changed files with 49 additions and 36 deletions
-6
View File
@@ -370,12 +370,6 @@
"!docs/releases/**",
"!docs/**/*.jpg",
"!docs/**/*.png",
"scripts/crabbox-routing-policy.mts",
"scripts/crabbox-wrapper-providers.mts",
"scripts/crabbox-wrapper.mjs",
"scripts/crabbox-wrapper.mts",
"scripts/testbox-lease-freshness.mts",
"scripts/lib/tsx-cli-shim.mjs",
"patches/",
"skills/",
"custodian-skills/",
+34 -18
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node
// Temporarily removes private workspace dependencies from the published manifest.
// Temporarily prepares source-only package metadata for publishing.
import { existsSync } from "node:fs";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
@@ -8,29 +8,45 @@ import { fileURLToPath } from "node:url";
const PACKAGE_JSON_PATH = "package.json";
const BACKUP_PATH = path.join(".artifacts", "package-manifest", "package.json.prepack-backup");
// Source checkouts use TS tooling; production installs omit dev dependencies.
// Rewrite only during prepack so published commands load the bundled runtime.
const CRABBOX_SOURCE_LAUNCHER = "node scripts/crabbox-wrapper.mjs";
const CRABBOX_PUBLISHED_LAUNCHER = "node dist/crabbox-wrapper.js";
function preparedPackageManifest(content) {
const packageJson = JSON.parse(content);
let changed = false;
for (const [name, command] of Object.entries(packageJson.scripts ?? {})) {
if (
typeof command === "string" &&
(command === CRABBOX_SOURCE_LAUNCHER || command.startsWith(`${CRABBOX_SOURCE_LAUNCHER} `))
) {
packageJson.scripts[name] =
`${CRABBOX_PUBLISHED_LAUNCHER}${command.slice(CRABBOX_SOURCE_LAUNCHER.length)}`;
changed = true;
}
}
const devDependencies = packageJson.devDependencies;
if (!devDependencies || typeof devDependencies !== "object" || Array.isArray(devDependencies)) {
return content;
if (devDependencies && typeof devDependencies === "object" && !Array.isArray(devDependencies)) {
const devDependencyEntries = Object.entries(devDependencies);
const publishedDevDependencyEntries = devDependencyEntries.filter(
([, spec]) => typeof spec !== "string" || !spec.startsWith("workspace:"),
);
if (publishedDevDependencyEntries.length !== devDependencyEntries.length) {
changed = true;
if (publishedDevDependencyEntries.length === 0) {
delete packageJson.devDependencies;
} else {
packageJson.devDependencies = Object.fromEntries(publishedDevDependencyEntries);
}
}
}
const devDependencyEntries = Object.entries(devDependencies);
const publishedDevDependencyEntries = devDependencyEntries.filter(
([, spec]) => typeof spec !== "string" || !spec.startsWith("workspace:"),
);
if (publishedDevDependencyEntries.length === devDependencyEntries.length) {
return content;
}
if (publishedDevDependencyEntries.length === 0) {
delete packageJson.devDependencies;
} else {
packageJson.devDependencies = Object.fromEntries(publishedDevDependencyEntries);
}
return `${JSON.stringify(packageJson, null, 2)}\n`;
return changed ? `${JSON.stringify(packageJson, null, 2)}\n` : content;
}
/** Restore package.json after prepack removed private workspace dependencies. */
/** Restore package.json after prepack prepared it for publishing. */
export async function restorePackageManifest(cwd = process.cwd()) {
const backupPath = path.join(cwd, BACKUP_PATH);
if (!existsSync(backupPath)) {
@@ -51,7 +67,7 @@ export async function restorePackageManifest(cwd = process.cwd()) {
return true;
}
/** Remove private workspace dependencies while recording restorable source bytes. */
/** Prepare published package metadata while recording restorable source bytes. */
export async function preparePackageManifest(cwd = process.cwd()) {
const packageJsonPath = path.join(cwd, PACKAGE_JSON_PATH);
const backupPath = path.join(cwd, BACKUP_PATH);
+3 -1
View File
@@ -224,6 +224,7 @@ describe("collectSourcePackWorkspaceDependencyErrors", () => {
scripts: {
prepack: "node scripts/package-manifest.mjs prepare",
postpack: "node scripts/package-manifest.mjs restore",
"crabbox:run": "node scripts/crabbox-wrapper.mjs run",
},
devDependencies: {
"@openclaw/session-url-contract": "workspace:*",
@@ -258,8 +259,9 @@ describe("collectSourcePackWorkspaceDependencyErrors", () => {
const packedPackageJson = JSON.parse(
readFileSync(path.join(extractDir, "package", "package.json"), "utf8"),
) as { devDependencies?: Record<string, string> };
) as { devDependencies?: Record<string, string>; scripts?: Record<string, string> };
expect(packedPackageJson.devDependencies).toEqual({ vitest: "4.1.10" });
expect(packedPackageJson.scripts?.["crabbox:run"]).toBe("node dist/crabbox-wrapper.js run");
expect(readFileSync(path.join(rootDir, "package.json"), "utf8")).toBe(originalPackageJson);
expect(
existsSync(
+11 -11
View File
@@ -64,19 +64,19 @@ describe("package manager build policy", () => {
expect(packageJson.files).toContain("THIRD_PARTY_NOTICES.md");
});
it("includes the Crabbox wrapper runtime modules in the published root package", () => {
it("omits source-only Crabbox wrapper modules from the published root package", () => {
const packageJson = readJson("package.json") as RootPackageJson;
expect(packageJson.files).toEqual(
expect.arrayContaining([
"scripts/crabbox-wrapper.mjs",
"scripts/crabbox-wrapper.mts",
"scripts/crabbox-wrapper-providers.mts",
"scripts/crabbox-routing-policy.mts",
"scripts/testbox-lease-freshness.mts",
"scripts/lib/tsx-cli-shim.mjs",
]),
);
for (const sourcePath of [
"scripts/crabbox-wrapper.mjs",
"scripts/crabbox-wrapper.mts",
"scripts/crabbox-wrapper-providers.mts",
"scripts/crabbox-routing-policy.mts",
"scripts/testbox-lease-freshness.mts",
"scripts/lib/tsx-cli-shim.mjs",
]) {
expect(packageJson.files).not.toContain(sourcePath);
}
});
it("pins forked transitive dependencies with parent-scoped npm-lock overrides", () => {
+1
View File
@@ -346,6 +346,7 @@ function buildCoreDistEntries(): Record<string, string> {
return {
index: "src/index.ts",
entry: "src/entry.ts",
"crabbox-wrapper": "scripts/crabbox-wrapper.mts",
"docker-healthcheck": "src/docker-healthcheck.ts",
// Ensure this module is bundled as an entry so legacy CLI shims can resolve its exports.
"cli/daemon-cli": "src/cli/daemon-cli.ts",