fix(ci): preserve cross-os candidate fallbacks

This commit is contained in:
Vincent Koc
2026-07-05 21:51:33 +02:00
parent 8a378fe84f
commit fdb112970f
2 changed files with 156 additions and 41 deletions
+97 -41
View File
@@ -699,49 +699,87 @@ async function prepareCandidate(params) {
sourceDir: params.sourceDir,
logPath: join(params.logsDir, "pnpm-pack-dry-run.log"),
});
logPhase("prepare", "package-candidate");
const packResult = await runCommand(
process.execPath,
[
join(params.sourceDir, "scripts", "package-openclaw-for-docker.mjs"),
"--skip-build",
"--source-dir",
params.sourceDir,
"--output-dir",
packDir,
],
{
cwd: params.sourceDir,
logPath: join(params.logsDir, "package-candidate.log"),
timeoutMs: 15 * 60 * 1000,
},
);
const packOutputLines = packResult.stdout.trim().split(/\r?\n/u).filter(Boolean);
const packedTarball = resolvePackDestinationTarball(
packOutputLines.at(-1),
const packCommand = resolvePackageCandidatePackCommand(params.sourceDir, packDir);
logPhase("prepare", packCommand.phase);
const packResult = await runCommand(packCommand.command, packCommand.args, {
cwd: params.sourceDir,
logPath: join(params.logsDir, packCommand.logFileName),
timeoutMs: 15 * 60 * 1000,
});
const packedCandidate = resolvePackedCandidateFromOutput({
output: packResult.stdout,
packDir,
"package-openclaw-for-docker",
);
writeFileSync(
packJsonPath,
`${JSON.stringify(
{
filename: packedTarball.fileName,
path: packedTarball.path,
version: packageJson.version,
},
null,
2,
)}\n`,
"utf8",
);
packageJson,
packCommand,
});
writeFileSync(packJsonPath, packedCandidate.packJson, "utf8");
return {
sourceDir: params.sourceDir,
sourceSha,
candidateVersion: String(packageJson.version ?? "").trim(),
candidateTgz: packedTarball.path,
candidateFileName: packedTarball.fileName,
candidateVersion: packedCandidate.version,
candidateTgz: packedCandidate.path,
candidateFileName: packedCandidate.fileName,
};
}
export function resolvePackageCandidatePackCommand(sourceDir, packDir) {
const packageHelper = join(sourceDir, "scripts", "package-openclaw-for-docker.mjs");
if (existsSync(packageHelper)) {
return {
args: [packageHelper, "--skip-build", "--source-dir", sourceDir, "--output-dir", packDir],
command: process.execPath,
kind: "docker-helper",
logFileName: "package-candidate.log",
phase: "package-candidate",
};
}
return {
args: ["pack", "--config.ignore-scripts=true", "--json", "--pack-destination", packDir],
command: pnpmCommand(),
kind: "pnpm-pack",
logFileName: "pnpm-pack.log",
phase: "pnpm-pack",
};
}
function resolvePackedCandidateFromOutput(params) {
if (params.packCommand.kind === "docker-helper") {
const packOutputLines = params.output.trim().split(/\r?\n/u).filter(Boolean);
const packedTarball = resolvePackDestinationTarball(
packOutputLines.at(-1),
params.packDir,
"package-openclaw-for-docker",
);
return {
fileName: packedTarball.fileName,
packJson: `${JSON.stringify(
{
filename: packedTarball.fileName,
path: packedTarball.path,
version: params.packageJson.version,
},
null,
2,
)}\n`,
path: packedTarball.path,
version: String(params.packageJson.version ?? "").trim(),
};
}
const parsedPack = JSON.parse(params.output);
const lastPack = Array.isArray(parsedPack) ? parsedPack.at(-1) : parsedPack;
const packedTarball = resolvePackDestinationTarball(
lastPack?.filename,
params.packDir,
"pnpm pack",
);
return {
fileName: packedTarball.fileName,
packJson: params.output,
path: packedTarball.path,
version: String(lastPack?.version ?? params.packageJson.version ?? "").trim(),
};
}
@@ -2973,7 +3011,9 @@ export function appendLatestNpmDebugLogTail(
env = process.env,
platform = process.platform,
) {
const candidates = resolveNpmDebugLogDirs(homeDir, env, platform).flatMap(findNpmDebugLogs);
const candidates = resolveNpmDebugLogDirs(homeDir, env, platform)
.flatMap(findNpmDebugLogs)
.toSorted((left, right) => left.mtimeMs - right.mtimeMs);
const latest = candidates.at(-1);
if (!latest) {
return "";
@@ -2993,8 +3033,16 @@ export function appendLatestNpmDebugLogTail(
}
export function resolveNpmDebugLogDirs(homeDir, env = process.env, platform = process.platform) {
const configuredLogsDir = String(env.npm_config_logs_dir ?? env.NPM_CONFIG_LOGS_DIR ?? "").trim();
const configuredCache = String(env.npm_config_cache ?? env.NPM_CONFIG_CACHE ?? "").trim();
const configuredLogsDir = resolveNpmConfiguredPath(
homeDir,
env.npm_config_logs_dir ?? env.NPM_CONFIG_LOGS_DIR,
platform,
);
const configuredCache = resolveNpmConfiguredPath(
homeDir,
env.npm_config_cache ?? env.NPM_CONFIG_CACHE,
platform,
);
const localAppData = String(env.LOCALAPPDATA ?? "").trim();
const logDirs = [
configuredLogsDir,
@@ -3005,6 +3053,14 @@ export function resolveNpmDebugLogDirs(homeDir, env = process.env, platform = pr
return [...new Set(logDirs)];
}
function resolveNpmConfiguredPath(homeDir, value, platform) {
const raw = String(value ?? "").trim();
if (!raw) {
return "";
}
return platform === "win32" ? pathWin32.resolve(homeDir, raw) : resolve(homeDir, raw);
}
function normalizeNpmCacheLogDir(logDir) {
return logDir.endsWith("/_logs") || logDir.endsWith("\\_logs") ? logDir : join(logDir, "_logs");
}
@@ -8,6 +8,7 @@ import {
realpathSync,
rmSync,
symlinkSync,
utimesSync,
writeFileSync,
} from "node:fs";
import { createConnection as createNetConnection, createServer as createNetServer } from "node:net";
@@ -77,6 +78,7 @@ import {
resolveNpmPackTarballFileName,
resolveNpmDebugLogDirs,
resolvePackDestinationTarball,
resolvePackageCandidatePackCommand,
resolveProviderConfig,
resolveDevUpdateVerificationRef,
resolveInstalledPrefixDirFromCliPath,
@@ -660,6 +662,33 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
}
});
it("falls back to pnpm pack for historical refs without the Docker package helper", () => {
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-pack-command-"));
try {
const packDir = join(dir, "out");
const fallback = resolvePackageCandidatePackCommand(dir, packDir);
expect(fallback).toMatchObject({
args: ["pack", "--config.ignore-scripts=true", "--json", "--pack-destination", packDir],
command: process.platform === "win32" ? "pnpm.cmd" : "pnpm",
kind: "pnpm-pack",
});
const helperPath = join(dir, "scripts", "package-openclaw-for-docker.mjs");
mkdirSync(dirname(helperPath), { recursive: true });
writeFileSync(helperPath, "export {};\n");
const helper = resolvePackageCandidatePackCommand(dir, packDir);
expect(helper).toMatchObject({
args: [helperPath, "--skip-build", "--source-dir", dir, "--output-dir", packDir],
command: process.execPath,
kind: "docker-helper",
});
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
it("keeps the Windows packaged-upgrade fallback install out of npm lifecycle scripts", () => {
const source = readFileSync("scripts/openclaw-cross-os-release-checks.ts", "utf8");
const fallbackInstallSource = source.slice(
@@ -1365,6 +1394,16 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
const logsDir = join(dir, "custom-logs");
const logPath = join(dir, "install.log");
mkdirSync(logsDir, { recursive: true });
mkdirSync(join(homeDir, ".npm", "_logs"), { recursive: true });
writeFileSync(
join(homeDir, ".npm", "_logs", "2026-07-05T00_00_00_000Z-debug-0.log"),
"old fallback log\n",
);
utimesSync(
join(homeDir, ".npm", "_logs", "2026-07-05T00_00_00_000Z-debug-0.log"),
new Date("2020-01-01T00:00:00Z"),
new Date("2020-01-01T00:00:00Z"),
);
writeFileSync(join(logsDir, "2026-07-05T00_00_00_000Z-debug-0.log"), "custom log\n");
writeFileSync(logPath, "install failed\n");
@@ -1378,6 +1417,26 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
}
});
it("resolves relative npm log config from the install working directory", () => {
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-npm-relative-logs-"));
try {
const homeDir = join(dir, "home");
const logsDir = join(homeDir, "relative-logs");
const cacheLogsDir = join(homeDir, "relative-cache", "_logs");
mkdirSync(logsDir, { recursive: true });
mkdirSync(cacheLogsDir, { recursive: true });
expect(resolveNpmDebugLogDirs(homeDir, { npm_config_logs_dir: "relative-logs" })).toContain(
logsDir,
);
expect(resolveNpmDebugLogDirs(homeDir, { npm_config_cache: "relative-cache" })).toContain(
cacheLogsDir,
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
it("kills timed-out command process groups", async () => {
if (process.platform === "win32") {
return;