mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 09:01:39 -06:00
fix(install): keep stable channel when npm install retries (#124778)
* fix(installer): keep npm channel target immutable Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * fix(install): fail after repeated CLI package install errors Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * fix(installer): verify npm package publication Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * fix(install): satisfy shellcheck for entry validation * fix(installer): link the packaged OpenClaw launcher Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae * test(installer): keep retry fixture version-valid Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae --------- Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
committed by
GitHub
parent
1027837d96
commit
2b2c11e748
@@ -19,6 +19,7 @@ import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
||||
import {
|
||||
writeNpmBeforePolicyFixture,
|
||||
writeNpmFreshnessConflictFixture,
|
||||
writeNpmInstallRetryFixture,
|
||||
} from "./install-npm-fixtures.js";
|
||||
|
||||
const SCRIPT_PATH = "scripts/install-cli.sh";
|
||||
@@ -41,6 +42,12 @@ function linkRequiredShellTools(bin: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function writeInstalledOpenClawEntry(nodeDir: string) {
|
||||
const entry = join(nodeDir, "lib", "node_modules", "openclaw", "dist", "entry.js");
|
||||
mkdirSync(join(entry, ".."), { recursive: true });
|
||||
writeFileSync(entry, "");
|
||||
}
|
||||
|
||||
describe("install-cli.sh", () => {
|
||||
const script = readFileSync(SCRIPT_PATH, "utf8");
|
||||
|
||||
@@ -494,7 +501,7 @@ describe("install-cli.sh", () => {
|
||||
"set -euo pipefail",
|
||||
`cd ${JSON.stringify(process.cwd())}`,
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
"install_node() { :; }",
|
||||
'install_node() { mkdir -p "$(node_dir)/lib/node_modules/openclaw/dist"; : > "$(node_dir)/lib/node_modules/openclaw/dist/entry.js"; }',
|
||||
"ensure_git() { :; }",
|
||||
'npm_bin() { printf "/usr/bin/true\\n"; }',
|
||||
`refresh_gateway_service_if_loaded() { touch ${JSON.stringify(refreshLog)}; }`,
|
||||
@@ -1442,6 +1449,7 @@ describe("install-cli.sh", () => {
|
||||
const nodeDir = join(tmp, "node");
|
||||
mkdirSync(bin, { recursive: true });
|
||||
mkdirSync(nodeDir, { recursive: true });
|
||||
writeInstalledOpenClawEntry(nodeDir);
|
||||
writeFileSync(npmrc, "min-release-age=7\n");
|
||||
const fakeNpm = join(bin, "npm");
|
||||
writeFileSync(
|
||||
@@ -1517,6 +1525,7 @@ describe("install-cli.sh", () => {
|
||||
mkdirSync(bin, { recursive: true });
|
||||
mkdirSync(home, { recursive: true });
|
||||
mkdirSync(nodeDir, { recursive: true });
|
||||
writeInstalledOpenClawEntry(nodeDir);
|
||||
if (source === "global") {
|
||||
mkdirSync(join(prefix, "etc"), { recursive: true });
|
||||
}
|
||||
@@ -1598,6 +1607,137 @@ describe("install-cli.sh", () => {
|
||||
expect(result.stdout).toContain("--install-method git --version main");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ requested: "latest", outcome: "success", error: "", calls: 1, status: 0 },
|
||||
{
|
||||
requested: "beta",
|
||||
outcome: "transient",
|
||||
error: "ECONNRESET socket hang up",
|
||||
calls: 2,
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
requested: "next",
|
||||
outcome: "transient",
|
||||
error: "ECONNRESET socket hang up",
|
||||
calls: 2,
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
requested: "2026.8.1",
|
||||
outcome: "transient",
|
||||
error: "ECONNRESET socket hang up",
|
||||
calls: 2,
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
requested: "latest",
|
||||
outcome: "persistent",
|
||||
error: "EACCES permission denied",
|
||||
calls: 2,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
requested: "beta",
|
||||
outcome: "persistent",
|
||||
error: "ENOSPC no space left",
|
||||
calls: 2,
|
||||
status: 1,
|
||||
},
|
||||
])(
|
||||
"keeps openclaw@$requested immutable across $outcome npm installs",
|
||||
({ requested, outcome, error, calls: expectedCalls, status }) => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-cli-npm-retry-"));
|
||||
const fakeNpm = join(tmp, "npm");
|
||||
const calls = join(tmp, "calls");
|
||||
const nodeDir = join(tmp, "node");
|
||||
const prefix = join(tmp, "prefix");
|
||||
writeNpmInstallRetryFixture(fakeNpm);
|
||||
|
||||
try {
|
||||
const result = runInstallCliShell(
|
||||
[
|
||||
"set -euo pipefail",
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
`npm_bin() { printf '%s\\n' ${JSON.stringify(fakeNpm)}; }`,
|
||||
`node_dir() { printf '%s\\n' ${JSON.stringify(nodeDir)}; }`,
|
||||
"npm_config_has_raw_key() { return 1; }",
|
||||
`PREFIX=${JSON.stringify(prefix)}`,
|
||||
`OPENCLAW_VERSION=${requested}`,
|
||||
"JSON=1",
|
||||
"set +e",
|
||||
"install_openclaw",
|
||||
"status=$?",
|
||||
'exit "$status"',
|
||||
].join("\n"),
|
||||
{
|
||||
NPM_FAKE_CALLS: calls,
|
||||
NPM_FAKE_ERROR: error,
|
||||
NPM_FAKE_OUTCOME: outcome,
|
||||
NPM_FAKE_PACKAGE_DIR: join(nodeDir, "lib", "node_modules", "openclaw"),
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(status);
|
||||
expect(readFileSync(calls, "utf8").trim().split("\n")).toEqual(
|
||||
Array.from({ length: expectedCalls }, () => `openclaw@${requested}`),
|
||||
);
|
||||
if (status !== 0) {
|
||||
expect(result.stderr).toContain(`${error} (attempt 2)`);
|
||||
expect(result.stdout).not.toContain('"status":"ok"');
|
||||
expect(existsSync(join(prefix, "bin", "openclaw"))).toBe(false);
|
||||
}
|
||||
if (requested !== "next") {
|
||||
expect(`${result.stdout}\n${result.stderr}`).not.toContain("openclaw@next");
|
||||
}
|
||||
} finally {
|
||||
rmSync(tmp, { force: true, recursive: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("fails after retrying the exact npm spec when npm exits zero without installing OpenClaw", () => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-cli-empty-success-"));
|
||||
const fakeNpm = join(tmp, "npm");
|
||||
const calls = join(tmp, "calls");
|
||||
const nodeDir = join(tmp, "node");
|
||||
const prefix = join(tmp, "prefix");
|
||||
writeNpmInstallRetryFixture(fakeNpm);
|
||||
|
||||
try {
|
||||
const result = runInstallCliShell(
|
||||
[
|
||||
"set -euo pipefail",
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
`npm_bin() { printf '%s\\n' ${JSON.stringify(fakeNpm)}; }`,
|
||||
`node_dir() { printf '%s\\n' ${JSON.stringify(nodeDir)}; }`,
|
||||
"npm_config_has_raw_key() { return 1; }",
|
||||
`PREFIX=${JSON.stringify(prefix)}`,
|
||||
"OPENCLAW_VERSION=latest",
|
||||
"JSON=1",
|
||||
"install_openclaw",
|
||||
].join("\n"),
|
||||
{
|
||||
NPM_FAKE_CALLS: calls,
|
||||
NPM_FAKE_ERROR: "",
|
||||
NPM_FAKE_OUTCOME: "success",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(readFileSync(calls, "utf8").trim().split("\n")).toEqual([
|
||||
"openclaw@latest",
|
||||
"openclaw@latest",
|
||||
]);
|
||||
expect(result.stdout).toContain("npm install did not produce a usable OpenClaw package");
|
||||
expect(result.stdout).not.toContain('"status":"ok"');
|
||||
expect(result.stdout).not.toContain("openclaw@next");
|
||||
expect(existsSync(join(prefix, "bin", "openclaw"))).toBe(false);
|
||||
} finally {
|
||||
rmSync(tmp, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not emit before args when npmrc min-release-age computes a before cutoff", () => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-cli-freshness-"));
|
||||
const prefix = join(tmp, "prefix");
|
||||
@@ -1606,6 +1746,7 @@ describe("install-cli.sh", () => {
|
||||
const argsLog = join(tmp, "npm-args.log");
|
||||
mkdirSync(nodeBin, { recursive: true });
|
||||
mkdirSync(home, { recursive: true });
|
||||
writeInstalledOpenClawEntry(join(prefix, "tools", "node-v24.15.0"));
|
||||
writeFileSync(join(home, ".npmrc"), "min-release-age=7\n");
|
||||
writeNpmFreshnessConflictFixture(join(nodeBin, "npm"), argsLog);
|
||||
|
||||
@@ -1643,6 +1784,7 @@ describe("install-cli.sh", () => {
|
||||
mkdirSync(nodeBin, { recursive: true });
|
||||
mkdirSync(home, { recursive: true });
|
||||
mkdirSync(project, { recursive: true });
|
||||
writeInstalledOpenClawEntry(join(prefix, "tools", "node-v24.15.0"));
|
||||
writeFileSync(join(home, ".npmrc"), "before=2026-01-01T00:00:00.000Z\n");
|
||||
writeFileSync(join(project, ".npmrc"), "min-release-age=7\n");
|
||||
writeNpmBeforePolicyFixture(join(nodeBin, "npm"), argsLog);
|
||||
|
||||
Reference in New Issue
Block a user