mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix: handle bin-only runtime deps
This commit is contained in:
@@ -485,5 +485,6 @@ function Main {
|
||||
return $true
|
||||
}
|
||||
|
||||
$installSucceeded = Main
|
||||
$mainResults = @(Main)
|
||||
$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true
|
||||
Complete-Install -Succeeded:$installSucceeded
|
||||
|
||||
@@ -213,6 +213,9 @@ function hasInstalledRuntimeDepExportFiles(packageDir: string, rawExports: unkno
|
||||
}
|
||||
|
||||
function hasInstalledRuntimeDepEntryFiles(packageDir: string, packageJson: JsonObject): boolean {
|
||||
if (hasInstalledRuntimeDepBinFiles(packageDir, packageJson.bin)) {
|
||||
return true;
|
||||
}
|
||||
if (packageJson.exports !== undefined) {
|
||||
return hasInstalledRuntimeDepExportFiles(packageDir, packageJson.exports);
|
||||
}
|
||||
@@ -223,6 +226,23 @@ function hasInstalledRuntimeDepEntryFiles(packageDir: string, packageJson: JsonO
|
||||
return hasRuntimeDepEntryFile(packageDir, "index");
|
||||
}
|
||||
|
||||
function collectRuntimeDepBinTargets(rawBin: unknown): string[] {
|
||||
if (typeof rawBin === "string" && rawBin.trim() !== "") {
|
||||
return [rawBin];
|
||||
}
|
||||
if (!isJsonObject(rawBin)) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(rawBin).filter(
|
||||
(value): value is string => typeof value === "string" && value.trim() !== "",
|
||||
);
|
||||
}
|
||||
|
||||
function hasInstalledRuntimeDepBinFiles(packageDir: string, rawBin: unknown): boolean {
|
||||
const targets = collectRuntimeDepBinTargets(rawBin);
|
||||
return targets.some((target) => hasRuntimeDepEntryFile(packageDir, target));
|
||||
}
|
||||
|
||||
function isRuntimeDepSatisfied(rootDir: string, dep: { name: string; version: string }): boolean {
|
||||
const installed = readInstalledRuntimeDepPackage(rootDir, dep.name);
|
||||
if (!installed) {
|
||||
|
||||
@@ -1458,6 +1458,32 @@ describe("createBundledRuntimeDepsPackagePlan config policy", () => {
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("accepts staged runtime deps that expose a package bin entry", () => {
|
||||
const installRoot = makeTempDir();
|
||||
const packageDir = path.join(installRoot, "node_modules", "@zed-industries", "codex-acp");
|
||||
fs.mkdirSync(path.join(packageDir, "bin"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "@zed-industries/codex-acp",
|
||||
version: "0.12.0",
|
||||
bin: {
|
||||
"codex-acp": "bin/codex-acp.js",
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(path.join(packageDir, "bin", "codex-acp.js"), "#!/usr/bin/env node\n");
|
||||
writeGeneratedRuntimeDepsManifest(installRoot, ["@zed-industries/codex-acp@0.12.0"]);
|
||||
|
||||
expect(isRuntimeDepsPlanMaterialized(installRoot, ["@zed-industries/codex-acp@0.12.0"])).toBe(
|
||||
true,
|
||||
);
|
||||
expect(() =>
|
||||
assertBundledRuntimeDepsInstalled(installRoot, ["@zed-industries/codex-acp@0.12.0"]),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("accepts staged runtime deps with exported package entry files", () => {
|
||||
const installRoot = makeTempDir();
|
||||
const packageDir = path.join(installRoot, "node_modules", "alpha-runtime");
|
||||
@@ -1645,6 +1671,29 @@ describe("createBundledRuntimeDepsPackagePlan config policy", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("reports staged runtime deps as missing when a package bin entry is absent", () => {
|
||||
const installRoot = makeTempDir();
|
||||
const packageDir = path.join(installRoot, "node_modules", "alpha-runtime");
|
||||
fs.mkdirSync(packageDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "alpha-runtime",
|
||||
version: "1.0.0",
|
||||
bin: {
|
||||
"alpha-runtime": "bin/alpha-runtime.js",
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
writeGeneratedRuntimeDepsManifest(installRoot, ["alpha-runtime@1.0.0"]);
|
||||
|
||||
expect(isRuntimeDepsPlanMaterialized(installRoot, ["alpha-runtime@1.0.0"])).toBe(false);
|
||||
expect(() => assertBundledRuntimeDepsInstalled(installRoot, ["alpha-runtime@1.0.0"])).toThrow(
|
||||
/alpha-runtime@1\.0\.0/,
|
||||
);
|
||||
});
|
||||
|
||||
it("reports staged runtime deps as missing when a declared entry file is absent", () => {
|
||||
const packageRoot = setupPolicyPackageRoot();
|
||||
const env = { OPENCLAW_PLUGIN_STAGE_DIR: makeTempDir() };
|
||||
|
||||
@@ -5,6 +5,8 @@ import { describe, expect, it } from "vitest";
|
||||
import { createScriptTestHarness } from "./test-helpers";
|
||||
|
||||
const SCRIPT_PATH = "scripts/install.ps1";
|
||||
const ENTRYPOINT_RE =
|
||||
/\r?\n\$mainResults = @\(Main\)\r?\n\$installSucceeded = \$mainResults\.Count -gt 0 -and \$mainResults\[-1\] -eq \$true\r?\nComplete-Install -Succeeded:\$installSucceeded\s*$/m;
|
||||
|
||||
function extractFunctionBody(source: string, name: string): string {
|
||||
const match = source.match(
|
||||
@@ -35,10 +37,7 @@ function toPowerShellSingleQuotedLiteral(value: string): string {
|
||||
}
|
||||
|
||||
function createFailingNodeFixture(source: string): string {
|
||||
const scriptWithoutEntryPoint = source.replace(
|
||||
/\r?\n\$installSucceeded = Main\r?\nComplete-Install -Succeeded:\$installSucceeded\s*$/m,
|
||||
"",
|
||||
);
|
||||
const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, "");
|
||||
expect(scriptWithoutEntryPoint).not.toBe(source);
|
||||
|
||||
return [
|
||||
@@ -48,7 +47,8 @@ function createFailingNodeFixture(source: string): string {
|
||||
"function Ensure-ExecutionPolicy { return $true }",
|
||||
"function Ensure-Node { return $false }",
|
||||
"",
|
||||
"$installSucceeded = Main",
|
||||
"$mainResults = @(Main)",
|
||||
"$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true",
|
||||
"Complete-Install -Succeeded:$installSucceeded",
|
||||
"",
|
||||
].join("\n");
|
||||
@@ -114,10 +114,7 @@ describe("install.ps1 failure handling", () => {
|
||||
runIfPowerShell("keeps npm chatter out of Main's success return value", () => {
|
||||
const tempDir = harness.createTempDir("openclaw-install-ps1-");
|
||||
const scriptPath = join(tempDir, "install.ps1");
|
||||
const scriptWithoutEntryPoint = source.replace(
|
||||
/\r?\n\$installSucceeded = Main\r?\nComplete-Install -Succeeded:\$installSucceeded\s*$/m,
|
||||
"",
|
||||
);
|
||||
const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, "");
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
[
|
||||
@@ -149,4 +146,46 @@ describe("install.ps1 failure handling", () => {
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
});
|
||||
|
||||
runIfPowerShell("uses Main's final boolean result when helper output precedes success", () => {
|
||||
const tempDir = harness.createTempDir("openclaw-install-ps1-");
|
||||
const scriptPath = join(tempDir, "install.ps1");
|
||||
const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, "");
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
[
|
||||
scriptWithoutEntryPoint,
|
||||
"",
|
||||
"function Write-Banner { }",
|
||||
"function Ensure-ExecutionPolicy { return $true }",
|
||||
"function Ensure-Node { return $true }",
|
||||
"function Ensure-Git { return $true }",
|
||||
"function Add-ToPath { param([string]$Path) }",
|
||||
"function Install-OpenClawNpm {",
|
||||
" param([string]$Target = 'latest')",
|
||||
" Write-Output 'native chatter'",
|
||||
" return $true",
|
||||
"}",
|
||||
"function Invoke-NativeCommandCapture {",
|
||||
" param([string]$FilePath, [string[]]$Arguments)",
|
||||
" return @{ ExitCode = 0; Stdout = 'npm prefix'; Stderr = '' }",
|
||||
"}",
|
||||
"$NoOnboard = $true",
|
||||
"$mainResults = @(Main)",
|
||||
"$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true",
|
||||
"Complete-Install -Succeeded:$installSucceeded",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
chmodSync(scriptPath, 0o755);
|
||||
|
||||
const result = spawnSync(
|
||||
powershell!,
|
||||
["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user