Archived
fix(runtime): align supported requirements and release hygiene
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
doctorExitCode,
|
||||
isCliEntrypoint,
|
||||
launchdRuntimeDetails,
|
||||
nodeVersionCheck,
|
||||
regularFileExists,
|
||||
serviceBackendForPlatform,
|
||||
} from "./cli.js";
|
||||
@@ -58,6 +59,18 @@ describe("commandWithVersionCheck", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("nodeVersionCheck", () => {
|
||||
it("checks the complete supported Node version with the resolved executable", () => {
|
||||
process.env["SHELL"] = "/bin/bash";
|
||||
|
||||
const command = nodeVersionCheck();
|
||||
|
||||
expect(command).toContain("22.19.0");
|
||||
expect(command).toContain("process.versions.node");
|
||||
expect(command).toContain("\"$pi_web_probe_executable\"");
|
||||
});
|
||||
});
|
||||
|
||||
describe("agentCommandForChecks", () => {
|
||||
it("reads the configured agent command for doctor checks", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "pi-web-cli-test-"));
|
||||
|
||||
+10
-6
@@ -15,6 +15,7 @@ import {
|
||||
type NativeServiceInstallFailure,
|
||||
} from "./nativeServices/serviceInstall.js";
|
||||
import {
|
||||
minimumSupportedNodeVersion,
|
||||
nativeServiceManagerRefs,
|
||||
productionNativeServiceIds,
|
||||
type NativeServiceBackend,
|
||||
@@ -772,11 +773,14 @@ export function commandWithVersionCheck(command: string): string {
|
||||
return `${found} && (${commandWord} --version 2>&1 || true)`;
|
||||
}
|
||||
|
||||
function nodeVersionCheck(): string {
|
||||
return [
|
||||
commandCheck("node"),
|
||||
"node -e \"const major = Number(process.versions.node.split('.')[0]); console.log(process.version); process.exit(major >= 22 ? 0 : 1);\"",
|
||||
].join(" && ");
|
||||
export function nodeVersionCheck(): string {
|
||||
return nativeServicePrerequisiteShellCheck(detectServiceShell().name, {
|
||||
id: "caller.node",
|
||||
kind: "node-version",
|
||||
command: "node",
|
||||
minimumVersion: minimumSupportedNodeVersion,
|
||||
description: `node >= ${minimumSupportedNodeVersion}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function agentCommandForChecks(env: NodeJS.ProcessEnv = process.env): string {
|
||||
@@ -787,7 +791,7 @@ function generalDoctorChecks(): Check[] {
|
||||
const shell = serviceShellLabel();
|
||||
const agentCommand = agentCommandForChecks();
|
||||
return [
|
||||
[`Caller login ${shell} can find node >= 22`, serviceShellCommand(nodeVersionCheck())],
|
||||
[`Caller login ${shell} can find node >= ${minimumSupportedNodeVersion}`, serviceShellCommand(nodeVersionCheck())],
|
||||
[`Caller login ${shell} can find npm`, serviceShellCommand(commandWithVersionCheck("npm"))],
|
||||
[`Caller login ${shell} can find ${agentCommand}`, serviceShellCommand(commandWithVersionCheck(agentCommand))],
|
||||
];
|
||||
|
||||
@@ -100,7 +100,7 @@ describe("production native service planning", () => {
|
||||
wants: [],
|
||||
prerequisites: [
|
||||
{ id: "sessiond.command.pi-web-sessiond", kind: "command-available", command: "pi-web-sessiond" },
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumMajor: 22 },
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumVersion: "22.19.0" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -111,7 +111,7 @@ describe("production native service planning", () => {
|
||||
wants: ["sessiond"],
|
||||
prerequisites: [
|
||||
{ id: "web.command.pi-web-server", kind: "command-available", command: "pi-web-server" },
|
||||
{ id: "web.node", kind: "node-version", command: "node", minimumMajor: 22 },
|
||||
{ id: "web.node", kind: "node-version", command: "node", minimumVersion: "22.19.0" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -196,7 +196,7 @@ describe("production native service planning", () => {
|
||||
namedCommandFailure: "command not found",
|
||||
},
|
||||
prerequisites: [
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumMajor: 22 },
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumVersion: "22.19.0" },
|
||||
{ id: "sessiond.entrypoint", kind: "readable-file", path: "/package with space/sessiond's entry.js" },
|
||||
],
|
||||
});
|
||||
@@ -324,7 +324,7 @@ describe("development native service planning", () => {
|
||||
environment: { PI_WEB_CONFIG: "/tmp/config.json" },
|
||||
workingDirectory: "/checkout with space",
|
||||
prerequisites: [
|
||||
{ id: "sessiond.node", kind: "node-version", minimumMajor: 22 },
|
||||
{ id: "sessiond.node", kind: "node-version", minimumVersion: "22.19.0" },
|
||||
{ id: "sessiond.command.npm", kind: "command-available", command: "npm" },
|
||||
{ id: "sessiond.package-scripts", kind: "package-scripts", scripts: ["start:sessiond"] },
|
||||
],
|
||||
@@ -338,7 +338,7 @@ describe("development native service planning", () => {
|
||||
after: ["sessiond"],
|
||||
wants: ["sessiond"],
|
||||
prerequisites: [
|
||||
{ id: "uiDev.node", kind: "node-version", minimumMajor: 22 },
|
||||
{ id: "uiDev.node", kind: "node-version", minimumVersion: "22.19.0" },
|
||||
{ id: "uiDev.command.npm", kind: "command-available", command: "npm" },
|
||||
{ id: "uiDev.command.bash", kind: "command-available", command: "bash" },
|
||||
{ id: "uiDev.package-scripts", kind: "package-scripts", scripts: ["dev:web", "dev:client"] },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const minimumSupportedNodeVersion = "22.19.0";
|
||||
|
||||
export type NativeServiceBackendKind = "systemd" | "launchd";
|
||||
export type NativeServiceMode = "production" | "development";
|
||||
export type NativeServiceId = "sessiond" | "web" | "uiDev";
|
||||
@@ -64,7 +66,7 @@ export type NativeServicePrerequisite =
|
||||
id: string;
|
||||
kind: "node-version";
|
||||
command: "node";
|
||||
minimumMajor: number;
|
||||
minimumVersion: string;
|
||||
description: string;
|
||||
}
|
||||
| {
|
||||
@@ -571,8 +573,8 @@ function nodeRequirement(serviceId: NativeServiceId): NativeServicePrerequisite
|
||||
id: `${serviceId}.node`,
|
||||
kind: "node-version",
|
||||
command: "node",
|
||||
minimumMajor: 22,
|
||||
description: "node >= 22 is available to the service shell",
|
||||
minimumVersion: minimumSupportedNodeVersion,
|
||||
description: `node >= ${minimumSupportedNodeVersion} is available to the service shell`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
LaunchdNativeServiceProbe,
|
||||
@@ -5,6 +6,7 @@ import {
|
||||
SystemdNativeServiceProbe,
|
||||
launchdProbePlist,
|
||||
nativeServicePrerequisiteShellCheck,
|
||||
nodeVersionCheckScript,
|
||||
systemdRunArguments,
|
||||
type LaunchdProbeFileSystem,
|
||||
type ProbeCommandResult,
|
||||
@@ -433,13 +435,26 @@ describe("probe service definitions", () => {
|
||||
id: "sessiond.node",
|
||||
kind: "node-version",
|
||||
command: "node",
|
||||
minimumMajor: 22,
|
||||
description: "node >= 22",
|
||||
minimumVersion: "22.19.0",
|
||||
description: "node >= 22.19.0",
|
||||
});
|
||||
expect(check).toContain("\"$pi_web_probe_executable\" '-e'");
|
||||
expect(check).toContain("22.19.0");
|
||||
expect(check).not.toContain("&& node -e");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ version: "21.99.99", accepted: false },
|
||||
{ version: "22.18.99", accepted: false },
|
||||
{ version: "22.19.0", accepted: true },
|
||||
{ version: "22.19.1", accepted: true },
|
||||
{ version: "23.0.0", accepted: true },
|
||||
])("checks the complete Node version for $version", ({ version, accepted }) => {
|
||||
const result = spawnSync(process.execPath, ["-e", nodeVersionCheckScript("22.19.0"), version]);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.status).toBe(accepted ? 0 : 1);
|
||||
});
|
||||
|
||||
it("requires bundled entrypoints to be readable regular files", () => {
|
||||
const check = nativeServicePrerequisiteShellCheck("bash", {
|
||||
id: "sessiond.entrypoint",
|
||||
|
||||
@@ -434,10 +434,8 @@ export function nativeServicePrerequisiteShellCheck(shell: NativeServiceShellNam
|
||||
switch (prerequisite.kind) {
|
||||
case "command-available":
|
||||
return externalExecutableShellCheck(shell, prerequisite.command);
|
||||
case "node-version": {
|
||||
const script = `const major=Number(process.versions.node.split('.')[0]);process.exit(major>=${String(prerequisite.minimumMajor)}?0:1)`;
|
||||
return externalExecutableShellCheck(shell, "node", ["-e", script]);
|
||||
}
|
||||
case "node-version":
|
||||
return externalExecutableShellCheck(shell, "node", ["-e", nodeVersionCheckScript(prerequisite.minimumVersion)]);
|
||||
case "readable-file": {
|
||||
const path = shellQuote(shell, prerequisite.path);
|
||||
return `test -f ${path} && test -r ${path}`;
|
||||
@@ -449,6 +447,11 @@ export function nativeServicePrerequisiteShellCheck(shell: NativeServiceShellNam
|
||||
}
|
||||
}
|
||||
|
||||
export function nodeVersionCheckScript(minimumVersion: string): string {
|
||||
const encodedMinimum = JSON.stringify(minimumVersion);
|
||||
return `const version=process.argv[1]??process.versions.node;console.log(process.version);const current=version.split('.').map(Number);const minimum=${encodedMinimum}.split('.').map(Number);const length=Math.max(current.length,minimum.length);let comparison=0;for(let index=0;index<length;index+=1){const left=current[index]??0;const right=minimum[index]??0;if(left!==right){comparison=left>right?1:-1;break}}process.exit(comparison>=0?0:1)`;
|
||||
}
|
||||
|
||||
function externalExecutableShellCheck(
|
||||
shell: NativeServiceShellName,
|
||||
command: string,
|
||||
@@ -518,7 +521,7 @@ function unsatisfiedDetail(prerequisite: NativeServicePrerequisite): string {
|
||||
case "command-available":
|
||||
return `${prerequisite.command} did not resolve to an external executable in the native service environment.`;
|
||||
case "node-version":
|
||||
return `node >= ${String(prerequisite.minimumMajor)} was not available in the native service environment.`;
|
||||
return `node >= ${prerequisite.minimumVersion} was not available in the native service environment.`;
|
||||
case "readable-file":
|
||||
return `${prerequisite.path} was not a readable regular file in the native service environment.`;
|
||||
case "package-scripts":
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
import { InMemoryCredentialStore, type AuthPrompt, type Credential } from "@earendil-works/pi-ai";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OAuthFlowState } from "../../shared/apiTypes.js";
|
||||
import { AuthService, type AuthChange, type AuthServiceLogger } from "./authService.js";
|
||||
import { AuthService, createModelRuntimeForAgentDir, type AuthChange, type AuthServiceLogger } from "./authService.js";
|
||||
import { OAuthLoginFlowService } from "./oauthLoginFlowService.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
@@ -251,7 +251,8 @@ describe("AuthService", () => {
|
||||
|
||||
it("stores credentials in the configured agent directory", async () => {
|
||||
const agentDir = await tempAgentDir();
|
||||
const auth = await AuthService.create({ agentDir });
|
||||
const runtime = await createModelRuntimeForAgentDir(agentDir, false);
|
||||
const auth = await AuthService.create({ runtime });
|
||||
|
||||
await auth.saveApiKey("anthropic", "sk-test");
|
||||
|
||||
|
||||
@@ -31,8 +31,12 @@ interface AuthChangeContext {
|
||||
|
||||
const noopLogger: AuthServiceLogger = { error() { /* no-op */ } };
|
||||
|
||||
export function createModelRuntimeForAgentDir(agentDir: string): Promise<ModelRuntime> {
|
||||
return ModelRuntime.create({ authPath: join(agentDir, "auth.json"), modelsPath: join(agentDir, "models.json") });
|
||||
export function createModelRuntimeForAgentDir(agentDir: string, allowModelNetwork?: boolean): Promise<ModelRuntime> {
|
||||
return ModelRuntime.create({
|
||||
authPath: join(agentDir, "auth.json"),
|
||||
modelsPath: join(agentDir, "models.json"),
|
||||
...(allowModelNetwork === undefined ? {} : { allowModelNetwork }),
|
||||
});
|
||||
}
|
||||
|
||||
export class AuthService {
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function seedCredential(store: InMemoryCredentialStore, providerId:
|
||||
* behavior (e.g. auth-loss warnings).
|
||||
*/
|
||||
export function createTestModelRuntime(credentials: CredentialStore = new InMemoryCredentialStore()): Promise<ModelRuntime> {
|
||||
return ModelRuntime.create({ credentials });
|
||||
return ModelRuntime.create({ credentials, modelsPath: null, allowModelNetwork: false });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user