diff --git a/scripts/check-openclaw-package-tarball.mjs b/scripts/check-openclaw-package-tarball.mjs index ce80c2e9a58..8466849fbd4 100644 --- a/scripts/check-openclaw-package-tarball.mjs +++ b/scripts/check-openclaw-package-tarball.mjs @@ -15,7 +15,7 @@ import { } from "./lib/package-dist-imports.mjs"; function usage() { - return "Usage: node scripts/check-openclaw-package-tarball.mjs "; + return "Usage: node scripts/check-openclaw-package-tarball.mjs [--require-bundled-workspace-deps] "; } function fail(message) { @@ -25,21 +25,29 @@ function fail(message) { function parseArgs(argv) { const args = argv[0] === "--" ? argv.slice(1) : argv; - const tarball = args[0]?.trim() ?? ""; - if (tarball === "--help" || tarball === "-h") { - return { help: true, tarball: "" }; + let requireBundledWorkspaceDeps = false; + let tarball = ""; + for (const rawArg of args) { + const arg = rawArg?.trim() ?? ""; + if (arg === "--help" || arg === "-h") { + return { help: true, requireBundledWorkspaceDeps: false, tarball: "" }; + } + if (arg === "--require-bundled-workspace-deps") { + requireBundledWorkspaceDeps = true; + continue; + } + if (arg.startsWith("-")) { + throw new Error(`Unknown OpenClaw package tarball check option: ${arg}`); + } + if (tarball) { + throw new Error(`Unexpected OpenClaw package tarball check argument: ${arg}`); + } + tarball = arg; } if (!tarball) { throw new Error(usage()); } - if (tarball.startsWith("-")) { - throw new Error(`Unknown OpenClaw package tarball check option: ${tarball}`); - } - const extraArg = args[1]?.trim(); - if (extraArg) { - throw new Error(`Unexpected OpenClaw package tarball check argument: ${extraArg}`); - } - return { help: false, tarball }; + return { help: false, requireBundledWorkspaceDeps, tarball }; } let cliArgs; @@ -293,7 +301,9 @@ if (entrySet.has("package.json")) { const packageJson = JSON.parse(readTarEntry("package.json")); packageVersion = typeof packageJson.version === "string" ? packageJson.version : ""; errors.push(...collectWorkspaceProtocolDependencyErrors(packageJson, "package.json")); - errors.push(...collectRequiredBundledWorkspaceDependencyErrors(packageJson, entrySet)); + if (cliArgs.requireBundledWorkspaceDeps) { + errors.push(...collectRequiredBundledWorkspaceDependencyErrors(packageJson, entrySet)); + } } catch { packageVersion = ""; } @@ -331,10 +341,7 @@ if (!entrySet.has("npm-shrinkwrap.json")) { errors.push("npm-shrinkwrap.json must not lock root devDependencies"); } errors.push( - ...collectWorkspaceProtocolDependencyErrors( - rootPackage, - "npm-shrinkwrap.json packages root", - ), + ...collectWorkspaceProtocolDependencyErrors(rootPackage, "npm-shrinkwrap.json packages root"), ); const devLockedPackages = Object.entries(shrinkwrap.packages ?? {}) .filter(([, packageMetadata]) => packageMetadata?.dev === true) diff --git a/scripts/openclaw-cross-os-release-checks.ts b/scripts/openclaw-cross-os-release-checks.ts index e7839d1adeb..d9394a2d1bd 100644 --- a/scripts/openclaw-cross-os-release-checks.ts +++ b/scripts/openclaw-cross-os-release-checks.ts @@ -2959,7 +2959,7 @@ async function installPackageSpec(params) { }, ); } catch (error) { - const debugTail = appendLatestNpmDebugLogTail(params.lane.homeDir, params.logPath); + const debugTail = appendLatestNpmDebugLogTail(params.lane.homeDir, params.logPath, installEnv); if (!debugTail) { throw error; } @@ -2967,9 +2967,13 @@ async function installPackageSpec(params) { } } -function appendLatestNpmDebugLogTail(homeDir, logPath) { - const logsDir = join(homeDir, ".npm", "_logs"); - const candidates = findNpmDebugLogs(logsDir); +export function appendLatestNpmDebugLogTail( + homeDir, + logPath, + env = process.env, + platform = process.platform, +) { + const candidates = resolveNpmDebugLogDirs(homeDir, env, platform).flatMap(findNpmDebugLogs); const latest = candidates.at(-1); if (!latest) { return ""; @@ -2988,6 +2992,17 @@ function appendLatestNpmDebugLogTail(homeDir, logPath) { return tail; } +export function resolveNpmDebugLogDirs(homeDir, env = process.env, platform = process.platform) { + const configuredCache = String(env.npm_config_cache ?? env.NPM_CONFIG_CACHE ?? "").trim(); + const localAppData = String(env.LOCALAPPDATA ?? "").trim(); + const cacheDirs = [ + configuredCache, + platform === "win32" && localAppData ? join(localAppData, "npm-cache") : "", + join(homeDir, ".npm"), + ].filter(Boolean); + return [...new Set(cacheDirs)].map((cacheDir) => join(cacheDir, "_logs")); +} + function findNpmDebugLogs(logsDir) { if (!existsSync(logsDir)) { return []; diff --git a/scripts/package-openclaw-for-docker.mjs b/scripts/package-openclaw-for-docker.mjs index 87b738dc2d6..74c8d9470dd 100644 --- a/scripts/package-openclaw-for-docker.mjs +++ b/scripts/package-openclaw-for-docker.mjs @@ -648,7 +648,11 @@ async function main() { const checkStartedAt = Date.now(); await run( "node", - [path.join(ROOT_DIR, "scripts/check-openclaw-package-tarball.mjs"), tarball], + [ + path.join(ROOT_DIR, "scripts/check-openclaw-package-tarball.mjs"), + "--require-bundled-workspace-deps", + tarball, + ], sourceDir, { timeoutMs: resolveTimeoutMs( diff --git a/test/scripts/check-openclaw-package-tarball.test.ts b/test/scripts/check-openclaw-package-tarball.test.ts index 63a7e6bb2da..50499422d52 100644 --- a/test/scripts/check-openclaw-package-tarball.test.ts +++ b/test/scripts/check-openclaw-package-tarball.test.ts @@ -90,7 +90,7 @@ describe("check-openclaw-package-tarball", () => { expect(result.status, result.stderr).toBe(0); expect(result.stdout).toContain( - "Usage: node scripts/check-openclaw-package-tarball.mjs ", + "Usage: node scripts/check-openclaw-package-tarball.mjs [--require-bundled-workspace-deps] ", ); expect(result.stderr).toBe(""); }); @@ -479,13 +479,32 @@ describe("check-openclaw-package-tarball", () => { ); }); - it("rejects private workspace dependencies that are not bundled", () => { + it("accepts separately published private workspace dependencies by default", () => { withTarball( ["dist/index.js"], { "dist/index.js": "export {};\n" }, (tarball) => { const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" }); + expect(result.status, result.stderr).toBe(0); + expect(result.stdout).toContain("OpenClaw package tarball integrity passed."); + }, + "2026.6.11", + { packageJson: { dependencies: { "@openclaw/ai": "2026.6.11" } } }, + ); + }); + + it("rejects private workspace dependencies that are not bundled when strict packaging requires it", () => { + withTarball( + ["dist/index.js"], + { "dist/index.js": "export {};\n" }, + (tarball) => { + const result = spawnSync( + "node", + [CHECK_SCRIPT, "--require-bundled-workspace-deps", tarball], + { encoding: "utf8" }, + ); + expect(result.status).not.toBe(0); expect(result.stderr).toContain( "package.json dependencies.@openclaw/ai must be listed in bundleDependencies because it is private to the OpenClaw workspace", diff --git a/test/scripts/openclaw-cross-os-release-checks.test.ts b/test/scripts/openclaw-cross-os-release-checks.test.ts index ef8a897db96..b76006cd4d8 100644 --- a/test/scripts/openclaw-cross-os-release-checks.test.ts +++ b/test/scripts/openclaw-cross-os-release-checks.test.ts @@ -31,6 +31,7 @@ import { buildWindowsFreshShellVersionCheckScript, buildInstalledBrowserOverrideImportProbeScript, buildNpmGlobalInstallArgs, + appendLatestNpmDebugLogTail, buildGatewayStatusArgsFromHelpText, buildWindowsPathBootstrapScript, canConnectToLoopbackPort, @@ -74,6 +75,7 @@ import { resolveInstalledCliInvocation, resolveInstalledPackageRootFromCliPath, resolveNpmPackTarballFileName, + resolveNpmDebugLogDirs, resolvePackDestinationTarball, resolveProviderConfig, resolveDevUpdateVerificationRef, @@ -1333,6 +1335,29 @@ describe("scripts/openclaw-cross-os-release-checks", () => { } }); + it("reads npm debug logs from the Windows cache root", () => { + const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-npm-debug-")); + try { + const homeDir = join(dir, "home"); + const localAppData = join(homeDir, "AppData", "Local"); + const logsDir = join(localAppData, "npm-cache", "_logs"); + const logPath = join(dir, "install.log"); + mkdirSync(logsDir, { recursive: true }); + writeFileSync(join(logsDir, "2026-07-05T00_00_00_000Z-debug-0.log"), "windows log\n"); + writeFileSync(logPath, "install failed\n"); + + expect(resolveNpmDebugLogDirs(homeDir, { LOCALAPPDATA: localAppData }, "win32")).toContain( + logsDir, + ); + expect( + appendLatestNpmDebugLogTail(homeDir, logPath, { LOCALAPPDATA: localAppData }, "win32"), + ).toContain("windows log"); + expect(readFileSync(logPath, "utf8")).toContain("windows log"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("kills timed-out command process groups", async () => { if (process.platform === "win32") { return;