fix(installer): avoid before with npm release-age configs (#85491)

Summary:
- The PR updates the Unix installers to avoid emitting npm `--before` when raw npm config contains `min-releas ...  records a changelog fix, and widens an internal model-catalog test helper type to accept sync auth checks.
- PR surface: Source +1, Tests +421, Docs +1, Other +150. Total +573 across 7 files.
- Reproducibility: yes. The linked report at https://github.com/openclaw/openclaw/issues/84743 gives an isolat ...  exclusivity, and current main still has the source path that can generate the conflicting `--before` flag.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(installer): avoid before with npm release-age configs
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8549…

Validation:
- ClawSweeper review passed for head fb0762f468.
- Required merge gates passed before the squash merge.

Prepared head SHA: fb0762f468
Review: https://github.com/openclaw/openclaw/pull/85491#issuecomment-4522229812

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
Andy Ye
2026-05-24 14:18:58 -07:00
committed by GitHub
parent 3e275a53dc
commit 4742db6c31
7 changed files with 580 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
import { spawnSync } from "node:child_process";
import { mkdtempSync, mkdirSync, readFileSync, rmSync } from "node:fs";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
@@ -127,10 +127,229 @@ describe("install-cli.sh", () => {
it("clears npm freshness filters for package installs", () => {
expect(script).toContain('freshness_flag="--min-release-age=0"');
expect(script).toContain('npm_raw_config_has_key "min-release-age"');
expect(script).toContain('freshness_flag="--before=$(date -u');
expect(script).toContain("env -u NPM_CONFIG_BEFORE -u npm_config_before");
});
it("does not emit --before when raw user npmrc config contains min-release-age", () => {
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-cli-npmrc-"));
const bin = join(tmp, "bin");
const npmrc = join(tmp, "user.npmrc");
const installArgs = join(tmp, "npm-install-args.txt");
const prefix = join(tmp, "prefix");
const nodeDir = join(tmp, "node");
mkdirSync(bin, { recursive: true });
mkdirSync(nodeDir, { recursive: true });
writeFileSync(npmrc, "min-release-age=7\n");
const fakeNpm = join(bin, "npm");
writeFileSync(
fakeNpm,
[
"#!/usr/bin/env bash",
'if [[ "$1" == "config" && "$2" == "get" ]]; then',
' if [[ "$3" == "min-release-age" ]]; then',
" printf 'null\\n'",
" exit 0",
" fi",
' if [[ "$3" == "before" ]]; then',
" printf '2026-01-01T00:00:00.000Z\\n'",
" exit 0",
" fi",
"fi",
'printf "%s\\n" "$@" > "$NPM_FAKE_INSTALL_ARGS"',
"exit 0",
"",
].join("\n"),
);
chmodSync(fakeNpm, 0o755);
try {
const result = runInstallCliShell(
[
"set -euo pipefail",
`cd ${JSON.stringify(process.cwd())}`,
`source ${JSON.stringify(SCRIPT_PATH)}`,
`npm_bin() { printf '%s\\n' ${JSON.stringify(fakeNpm)}; }`,
`node_dir() { printf '%s\\n' ${JSON.stringify(nodeDir)}; }`,
"emit_json() { :; }",
"log() { :; }",
`PREFIX=${JSON.stringify(prefix)}`,
"SET_NPM_PREFIX=0",
"OPENCLAW_VERSION=1.2.3",
"install_openclaw",
].join("\n"),
{
NPM_CONFIG_USERCONFIG: npmrc,
NPM_FAKE_INSTALL_ARGS: installArgs,
PATH: `${bin}:${process.env.PATH}`,
},
);
expect(result.status).toBe(0);
expect(readFileSync(installArgs, "utf8")).toContain("--min-release-age=0\n");
expect(readFileSync(installArgs, "utf8")).not.toContain("--before=");
} finally {
rmSync(tmp, { force: true, recursive: true });
}
});
it("does not emit --before when default global npmrc config contains min-release-age", () => {
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-cli-global-npmrc-"));
const bin = join(tmp, "bin");
const home = join(tmp, "home");
const prefix = join(tmp, "prefix");
const npmrc = join(prefix, "etc", "npmrc");
const calls = join(tmp, "npm-calls.txt");
const installArgs = join(tmp, "npm-install-args.txt");
const installPrefix = join(tmp, "install-prefix");
const nodeDir = join(tmp, "node");
mkdirSync(bin, { recursive: true });
mkdirSync(home, { recursive: true });
mkdirSync(nodeDir, { recursive: true });
mkdirSync(join(prefix, "etc"), { recursive: true });
writeFileSync(npmrc, "min-release-age=7\n");
const fakeNpm = join(bin, "npm");
writeFileSync(
fakeNpm,
[
"#!/usr/bin/env bash",
'printf "%s\\n" "$*" >> "$NPM_FAKE_CALLS"',
'if [[ "$1" == "config" && "$2" == "get" ]]; then',
' if [[ "$3" == "min-release-age" ]]; then',
" printf 'null\\n'",
" exit 0",
" fi",
' if [[ "$3" == "globalconfig" ]]; then',
' printf "%s\\n" "$NPM_FAKE_GLOBALCONFIG"',
" exit 0",
" fi",
' if [[ "$3" == "before" ]]; then',
" printf '2026-01-01T00:00:00.000Z\\n'",
" exit 0",
" fi",
"fi",
'printf "%s\\n" "$@" > "$NPM_FAKE_INSTALL_ARGS"',
"exit 0",
"",
].join("\n"),
);
chmodSync(fakeNpm, 0o755);
try {
const result = runInstallCliShell(
[
"set -euo pipefail",
`cd ${JSON.stringify(process.cwd())}`,
`source ${JSON.stringify(SCRIPT_PATH)}`,
`npm_bin() { printf '%s\\n' ${JSON.stringify(fakeNpm)}; }`,
`node_dir() { printf '%s\\n' ${JSON.stringify(nodeDir)}; }`,
"emit_json() { :; }",
"log() { :; }",
`PREFIX=${JSON.stringify(installPrefix)}`,
"SET_NPM_PREFIX=0",
"OPENCLAW_VERSION=1.2.3",
"install_openclaw",
].join("\n"),
{
HOME: home,
NPM_CONFIG_GLOBALCONFIG: undefined,
NPM_CONFIG_PREFIX: undefined,
npm_config_globalconfig: undefined,
npm_config_prefix: undefined,
NPM_FAKE_CALLS: calls,
NPM_FAKE_GLOBALCONFIG: npmrc,
NPM_FAKE_INSTALL_ARGS: installArgs,
PATH: `${bin}:${process.env.PATH}`,
},
);
expect(result.status).toBe(0);
expect(readFileSync(installArgs, "utf8")).toContain("--min-release-age=0\n");
expect(readFileSync(installArgs, "utf8")).not.toContain("--before=");
expect(readFileSync(calls, "utf8")).not.toContain("config get before");
} finally {
rmSync(tmp, { force: true, recursive: true });
}
});
it("does not emit --before when builtin npmrc config contains min-release-age", () => {
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-cli-builtin-npmrc-"));
const bin = join(tmp, "bin");
const home = join(tmp, "home");
const npmrc = join(tmp, "npmrc");
const calls = join(tmp, "npm-calls.txt");
const installArgs = join(tmp, "npm-install-args.txt");
const installPrefix = join(tmp, "install-prefix");
const nodeDir = join(tmp, "node");
mkdirSync(bin, { recursive: true });
mkdirSync(home, { recursive: true });
mkdirSync(nodeDir, { recursive: true });
writeFileSync(npmrc, "min-release-age=7\n");
const fakeNpm = join(bin, "npm");
writeFileSync(
fakeNpm,
[
"#!/usr/bin/env bash",
'printf "%s\\n" "$*" >> "$NPM_FAKE_CALLS"',
'if [[ "$1" == "config" && "$2" == "get" ]]; then',
' if [[ "$3" == "min-release-age" ]]; then',
" printf 'null\\n'",
" exit 0",
" fi",
' if [[ "$3" == "globalconfig" ]]; then',
' printf "%s\\n" "$NPM_FAKE_GLOBALCONFIG"',
" exit 0",
" fi",
' if [[ "$3" == "before" ]]; then',
" printf '2026-01-01T00:00:00.000Z\\n'",
" exit 0",
" fi",
"fi",
'printf "%s\\n" "$@" > "$NPM_FAKE_INSTALL_ARGS"',
"exit 0",
"",
].join("\n"),
);
chmodSync(fakeNpm, 0o755);
try {
const result = runInstallCliShell(
[
"set -euo pipefail",
`cd ${JSON.stringify(process.cwd())}`,
`source ${JSON.stringify(SCRIPT_PATH)}`,
`npm_bin() { printf '%s\\n' ${JSON.stringify(fakeNpm)}; }`,
`node_dir() { printf '%s\\n' ${JSON.stringify(nodeDir)}; }`,
"emit_json() { :; }",
"log() { :; }",
`PREFIX=${JSON.stringify(installPrefix)}`,
"SET_NPM_PREFIX=0",
"OPENCLAW_VERSION=1.2.3",
"install_openclaw",
].join("\n"),
{
HOME: home,
NPM_CONFIG_GLOBALCONFIG: undefined,
NPM_CONFIG_PREFIX: undefined,
npm_config_globalconfig: undefined,
npm_config_prefix: undefined,
NPM_FAKE_CALLS: calls,
NPM_FAKE_GLOBALCONFIG: join(tmp, "missing-global-npmrc"),
NPM_FAKE_INSTALL_ARGS: installArgs,
PATH: `${bin}:${process.env.PATH}`,
},
);
expect(result.status).toBe(0);
expect(readFileSync(installArgs, "utf8")).toContain("--min-release-age=0\n");
expect(readFileSync(installArgs, "utf8")).not.toContain("--before=");
expect(readFileSync(calls, "utf8")).not.toContain("config get before");
} finally {
rmSync(tmp, { force: true, recursive: true });
}
});
it("rejects OpenClaw GitHub source targets for npm installs", () => {
const result = runInstallCliShell(`
set -euo pipefail