refactor(cli): define canonical native service plans

This commit is contained in:
Federico Jaramillo Martinez
2026-07-13 00:08:09 +02:00
parent d72b14f40a
commit 5ce793add9
2 changed files with 896 additions and 0 deletions
+356
View File
@@ -0,0 +1,356 @@
import { describe, expect, it, vi } from "vitest";
import {
createDevelopmentNativeServicePlan,
planValidationProbeRequests,
resolveProductionNativeServicePlan,
type NativeServiceAuthoritativeProbe,
type NativeServiceProbeRequest,
type NativeServiceProbeResult,
type ProductionNativeServicePlanInput,
} from "./servicePlan.js";
const backend = { kind: "systemd", label: "systemd user services" } as const;
const shell = {
name: "zsh",
executable: "/bin/zsh",
source: "detected",
detectedExecutable: "/bin/zsh",
} as const;
function productionInput(): ProductionNativeServicePlanInput {
return {
backend,
shell,
environment: { PI_WEB_CONFIG: "/home/user/.config/pi-web/config.json" },
executables: {
sessiond: {
configuredCommand: undefined,
namedCommand: "pi-web-sessiond",
bundledEntrypointPath: "/package/dist/server/sessiond.js",
},
web: {
configuredCommand: undefined,
namedCommand: "pi-web-server",
bundledEntrypointPath: "/package/dist/server/index.js",
},
},
};
}
function completedProbe(status: "satisfied" | "unsatisfied", detail: string | null = null): NativeServiceAuthoritativeProbe {
return {
run: (request) => Promise.resolve({
kind: "completed",
outcomes: request.prerequisites.map((prerequisite) => ({ prerequisiteId: prerequisite.id, status, detail })),
}),
};
}
describe("production native service planning", () => {
it("selects named commands from one authoritative backend probe and carries their exact requirements", async () => {
const requests: NativeServiceProbeRequest[] = [];
const probe: NativeServiceAuthoritativeProbe = {
run: (request) => {
requests.push(request);
return Promise.resolve({
kind: "completed",
outcomes: request.prerequisites.map((prerequisite) => ({
prerequisiteId: prerequisite.id,
status: "satisfied",
detail: "/usr/local/bin/example",
})),
});
},
};
const resolution = await resolveProductionNativeServicePlan(productionInput(), {
probe,
fileExists: () => false,
});
expect(requests).toEqual([
{
purpose: "executable-selection",
backend,
shell,
environment: { PI_WEB_CONFIG: "/home/user/.config/pi-web/config.json" },
workingDirectory: null,
prerequisites: [
expect.objectContaining({ id: "sessiond.command.pi-web-sessiond", kind: "command-available", command: "pi-web-sessiond" }),
expect.objectContaining({ id: "web.command.pi-web-server", kind: "command-available", command: "pi-web-server" }),
],
},
]);
expect(resolution.ok).toBe(true);
if (!resolution.ok) throw new Error(JSON.stringify(resolution.failures));
expect(resolution.plan).toMatchObject({
mode: "production",
backend,
shell,
services: [
{
id: "sessiond",
manager: { systemdName: "pi-web-sessiond.service", launchdLabel: "com.pi-web.sessiond" },
shellCommand: "exec pi-web-sessiond",
strategy: { kind: "named-command", command: "pi-web-sessiond", selectedBy: "authoritative-backend-probe" },
environment: { PI_WEB_CONFIG: "/home/user/.config/pi-web/config.json" },
workingDirectory: null,
after: [],
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: "web",
shellCommand: "exec pi-web-server",
strategy: { kind: "named-command", command: "pi-web-server" },
after: ["sessiond"],
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 },
],
},
],
});
expect(planValidationProbeRequests(resolution.plan)).toMatchObject([
{
purpose: "plan-validation",
backend,
shell,
workingDirectory: null,
prerequisites: [{ id: "sessiond.command.pi-web-sessiond" }, { id: "sessiond.node" }],
},
{
purpose: "plan-validation",
backend,
shell,
workingDirectory: null,
prerequisites: [{ id: "web.command.pi-web-server" }, { id: "web.node" }],
},
]);
});
it("preserves configured overrides verbatim and never probes or executes them", async () => {
const input = productionInput();
input.executables.sessiond.configuredCommand = " /opt/pi web/run-sessiond --flag ";
input.executables.web.configuredCommand = "custom-web --serve";
const run = vi.fn<(request: NativeServiceProbeRequest) => Promise<NativeServiceProbeResult>>();
const fileExists = vi.fn<(path: string) => boolean>();
const resolution = await resolveProductionNativeServicePlan(input, { probe: { run }, fileExists });
expect(run).not.toHaveBeenCalled();
expect(fileExists).not.toHaveBeenCalled();
expect(resolution.ok).toBe(true);
if (!resolution.ok) throw new Error(JSON.stringify(resolution.failures));
expect(resolution.plan.services).toMatchObject([
{
id: "sessiond",
shellCommand: "exec /opt/pi web/run-sessiond --flag ",
strategy: {
kind: "configured-override",
command: " /opt/pi web/run-sessiond --flag ",
verification: "unverified",
},
prerequisites: [],
},
{
id: "web",
shellCommand: "exec custom-web --serve",
strategy: { kind: "configured-override", command: "custom-web --serve", verification: "unverified" },
prerequisites: [],
},
]);
expect(planValidationProbeRequests(resolution.plan)).toEqual([]);
});
it("falls back per service to bundled entrypoints when named commands are unavailable", async () => {
const input = productionInput();
input.executables.sessiond.bundledEntrypointPath = "/package with space/sessiond's entry.js";
const fileExists = vi.fn<(path: string) => boolean>(() => true);
const resolution = await resolveProductionNativeServicePlan(input, {
probe: completedProbe("unsatisfied", "command not found"),
fileExists,
});
expect(fileExists).toHaveBeenCalledTimes(2);
expect(resolution.ok).toBe(true);
if (!resolution.ok) throw new Error(JSON.stringify(resolution.failures));
expect(resolution.plan.services[0]).toMatchObject({
shellCommand: "exec node '/package with space/sessiond'\\''s entry.js'",
strategy: {
kind: "bundled-entrypoint",
command: "node",
namedCommand: "pi-web-sessiond",
namedCommandFailure: "command not found",
},
prerequisites: [
{ id: "sessiond.node", kind: "node-version", command: "node", minimumMajor: 22 },
{ id: "sessiond.entrypoint", kind: "readable-file", path: "/package with space/sessiond's entry.js" },
],
});
});
it("mixes configured, named, and bundled decisions without unrelated checks", async () => {
const input = productionInput();
input.executables.sessiond.configuredCommand = "custom-sessiond";
const requests: NativeServiceProbeRequest[] = [];
const resolution = await resolveProductionNativeServicePlan(input, {
probe: {
run: (request) => {
requests.push(request);
return Promise.resolve({
kind: "completed",
outcomes: [{ prerequisiteId: "web.command.pi-web-server", status: "unsatisfied", detail: null }],
});
},
},
fileExists: () => true,
});
expect(requests[0]?.prerequisites).toMatchObject([{ id: "web.command.pi-web-server", command: "pi-web-server" }]);
expect(resolution.ok).toBe(true);
if (resolution.ok) {
expect(resolution.plan.services.map((service) => service.strategy.kind)).toEqual(["configured-override", "bundled-entrypoint"]);
}
});
it("returns structured failures when neither production executable strategy is viable", async () => {
const resolution = await resolveProductionNativeServicePlan(productionInput(), {
probe: completedProbe("unsatisfied", "not found in service PATH"),
fileExists: () => false,
});
expect(resolution).toEqual({
ok: false,
failures: [
{
kind: "executable-unavailable",
serviceId: "sessiond",
namedCommand: "pi-web-sessiond",
namedCommandFailure: "not found in service PATH",
bundledEntrypointPath: "/package/dist/server/sessiond.js",
},
{
kind: "executable-unavailable",
serviceId: "web",
namedCommand: "pi-web-server",
namedCommandFailure: "not found in service PATH",
bundledEntrypointPath: "/package/dist/server/index.js",
},
],
});
});
it("does not reinterpret probe infrastructure failures as missing commands", async () => {
const fileExists = vi.fn<(path: string) => boolean>(() => true);
const resolution = await resolveProductionNativeServicePlan(productionInput(), {
probe: {
run: () => Promise.resolve({ kind: "infrastructure-failure", message: "launchd probe cleanup failed" }),
},
fileExists,
});
expect(fileExists).not.toHaveBeenCalled();
expect(resolution).toEqual({
ok: false,
failures: [{
kind: "probe-infrastructure",
serviceIds: ["sessiond", "web"],
message: "launchd probe cleanup failed",
}],
});
});
it("treats thrown and malformed probe results as infrastructure failures", async () => {
const thrown = await resolveProductionNativeServicePlan(productionInput(), {
probe: { run: () => Promise.reject(new Error("systemd-run failed")) },
fileExists: () => true,
});
expect(thrown).toMatchObject({
ok: false,
failures: [{ kind: "probe-infrastructure", message: "systemd-run failed" }],
});
const malformed = await resolveProductionNativeServicePlan(productionInput(), {
probe: {
run: () => Promise.resolve({
kind: "completed",
outcomes: [{ prerequisiteId: "sessiond.command.pi-web-sessiond", status: "satisfied", detail: null }],
}),
},
fileExists: () => true,
});
expect(malformed).toMatchObject({
ok: false,
failures: [{ kind: "probe-infrastructure", message: "Authoritative probe returned no outcome for web.command.pi-web-server." }],
});
});
});
describe("development native service planning", () => {
it("plans only the exact checkout commands and prerequisites", () => {
const plan = createDevelopmentNativeServicePlan({
backend: { kind: "launchd", label: "LaunchAgents" },
shell: { name: "fish", executable: "/opt/homebrew/bin/fish", source: "detected", detectedExecutable: "/opt/homebrew/bin/fish" },
environment: { PI_WEB_CONFIG: "/tmp/config.json" },
workingDirectory: "/checkout with space",
packageJsonPath: "/checkout with space/package.json",
});
expect(plan).toMatchObject({
mode: "development",
backend: { kind: "launchd" },
shell: { name: "fish", executable: "/opt/homebrew/bin/fish" },
services: [
{
id: "sessiond",
shellCommand: "exec npm run start:sessiond",
strategy: { kind: "development-npm-script", script: "start:sessiond" },
restart: "never",
environment: { PI_WEB_CONFIG: "/tmp/config.json" },
workingDirectory: "/checkout with space",
prerequisites: [
{ id: "sessiond.node", kind: "node-version", minimumMajor: 22 },
{ id: "sessiond.command.npm", kind: "command-available", command: "npm" },
{ id: "sessiond.package-scripts", kind: "package-scripts", scripts: ["start:sessiond"] },
],
},
{
id: "uiDev",
shellCommand: "exec /usr/bin/env bash -c 'trap \"kill 0\" EXIT; npm run dev:web & npm run dev:client & wait'",
strategy: { kind: "development-npm-script-group", scripts: ["dev:web", "dev:client"], interpreter: "bash" },
restart: "never",
workingDirectory: "/checkout with space",
after: ["sessiond"],
wants: ["sessiond"],
prerequisites: [
{ id: "uiDev.node", kind: "node-version", minimumMajor: 22 },
{ 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"] },
],
},
],
});
const serviceCommandRequirements = plan.services.flatMap((service) => service.prerequisites)
.filter((prerequisite) => prerequisite.kind === "command-available")
.map((prerequisite) => prerequisite.command);
expect(serviceCommandRequirements).toEqual(["npm", "npm", "bash"]);
expect(serviceCommandRequirements).not.toContain("pi-web-server");
expect(serviceCommandRequirements).not.toContain("pi-web-sessiond");
expect(planValidationProbeRequests(plan)).toMatchObject([
{ backend: { kind: "launchd" }, workingDirectory: "/checkout with space" },
{ backend: { kind: "launchd" }, workingDirectory: "/checkout with space" },
]);
});
});
+540
View File
@@ -0,0 +1,540 @@
export type NativeServiceBackendKind = "systemd" | "launchd";
export type NativeServiceMode = "production" | "development";
export type NativeServiceId = "sessiond" | "web" | "uiDev";
export type ProductionNativeServiceId = Extract<NativeServiceId, "sessiond" | "web">;
export type NativeServiceShellName = "bash" | "zsh" | "fish";
export type NativeServiceRestartPolicy = "on-failure" | "never";
export interface NativeServiceBackend {
kind: NativeServiceBackendKind;
label: string;
}
export interface NativeServiceShell {
name: NativeServiceShellName;
executable: string;
source: "detected" | "fallback";
detectedExecutable: string | null;
}
export interface NativeServiceManagerRef {
systemdName: string;
launchdLabel: string;
launchdPlistName: string;
logName: string;
}
export type NativeServiceCommandStrategy =
| {
kind: "configured-override";
command: string;
verification: "unverified";
}
| {
kind: "named-command";
command: string;
selectedBy: "authoritative-backend-probe";
}
| {
kind: "bundled-entrypoint";
command: "node";
entrypointPath: string;
namedCommand: string;
namedCommandFailure: string | null;
}
| {
kind: "development-npm-script";
script: string;
}
| {
kind: "development-npm-script-group";
scripts: readonly string[];
interpreter: "bash";
};
export type NativeServicePrerequisite =
| {
id: string;
kind: "command-available";
command: string;
description: string;
}
| {
id: string;
kind: "node-version";
command: "node";
minimumMajor: number;
description: string;
}
| {
id: string;
kind: "readable-file";
path: string;
description: string;
}
| {
id: string;
kind: "package-scripts";
packageJsonPath: string;
scripts: readonly string[];
description: string;
};
export interface NativeServicePlanService {
id: NativeServiceId;
manager: NativeServiceManagerRef;
description: string;
shellCommand: string;
strategy: NativeServiceCommandStrategy;
restart: NativeServiceRestartPolicy;
environment: Readonly<Record<string, string>>;
workingDirectory: string | null;
after: readonly NativeServiceId[];
wants: readonly NativeServiceId[];
prerequisites: readonly NativeServicePrerequisite[];
}
export interface NativeServicePlan {
mode: NativeServiceMode;
backend: NativeServiceBackend;
shell: NativeServiceShell;
services: readonly NativeServicePlanService[];
}
export interface NativeServiceProbeRequest {
purpose: "executable-selection" | "plan-validation";
backend: NativeServiceBackend;
shell: NativeServiceShell;
environment: Readonly<Record<string, string>>;
workingDirectory: string | null;
prerequisites: readonly NativeServicePrerequisite[];
}
export interface NativeServicePrerequisiteOutcome {
prerequisiteId: string;
status: "satisfied" | "unsatisfied";
detail: string | null;
}
export type NativeServiceProbeResult =
| {
kind: "completed";
outcomes: readonly NativeServicePrerequisiteOutcome[];
}
| {
kind: "infrastructure-failure";
message: string;
};
/**
* Runs requirements in the real native service-manager context represented by
* the request. Implementations must not treat the caller shell or a simulated
* `env -i` environment as authoritative. Timeouts, manager failures, malformed
* output, and cleanup failures are infrastructure failures; a missing command
* is a completed probe with an unsatisfied outcome.
*/
export interface NativeServiceAuthoritativeProbe {
run(request: NativeServiceProbeRequest): Promise<NativeServiceProbeResult>;
}
export interface ProductionNativeServiceExecutableInput {
configuredCommand: string | undefined;
namedCommand: string;
bundledEntrypointPath: string;
}
export interface ProductionNativeServicePlanInput {
backend: NativeServiceBackend;
shell: NativeServiceShell;
environment: Readonly<Record<string, string>>;
executables: Readonly<Record<ProductionNativeServiceId, ProductionNativeServiceExecutableInput>>;
}
export interface DevelopmentNativeServicePlanInput {
backend: NativeServiceBackend;
shell: NativeServiceShell;
environment: Readonly<Record<string, string>>;
workingDirectory: string;
packageJsonPath: string;
}
export interface NativeServicePlanDependencies {
probe: NativeServiceAuthoritativeProbe;
fileExists(path: string): boolean;
}
export type NativeServicePlanFailure =
| {
kind: "probe-infrastructure";
serviceIds: readonly ProductionNativeServiceId[];
message: string;
}
| {
kind: "entrypoint-inspection-failure";
serviceId: ProductionNativeServiceId;
entrypointPath: string;
message: string;
}
| {
kind: "executable-unavailable";
serviceId: ProductionNativeServiceId;
namedCommand: string;
namedCommandFailure: string | null;
bundledEntrypointPath: string;
};
export type NativeServicePlanResolution =
| { ok: true; plan: NativeServicePlan }
| { ok: false; failures: readonly NativeServicePlanFailure[] };
const nativeServiceRefs: Readonly<Record<NativeServiceId, NativeServiceManagerRef>> = {
sessiond: {
systemdName: "pi-web-sessiond.service",
launchdLabel: "com.pi-web.sessiond",
launchdPlistName: "com.pi-web.sessiond.plist",
logName: "sessiond.log",
},
web: {
systemdName: "pi-web.service",
launchdLabel: "com.pi-web.web",
launchdPlistName: "com.pi-web.web.plist",
logName: "web.log",
},
uiDev: {
systemdName: "pi-web-ui-dev.service",
launchdLabel: "com.pi-web.ui-dev",
launchdPlistName: "com.pi-web.ui-dev.plist",
logName: "ui-dev.log",
},
};
const productionServiceIds = ["sessiond", "web"] as const satisfies readonly ProductionNativeServiceId[];
export async function resolveProductionNativeServicePlan(
input: ProductionNativeServicePlanInput,
dependencies: NativeServicePlanDependencies,
): Promise<NativeServicePlanResolution> {
const configuredStrategies = new Map<ProductionNativeServiceId, NativeServiceCommandStrategy>();
const selectionRequirements: NativeServicePrerequisite[] = [];
const serviceIdsToProbe: ProductionNativeServiceId[] = [];
for (const serviceId of productionServiceIds) {
const executable = input.executables[serviceId];
if (hasConfiguredCommand(executable.configuredCommand)) {
configuredStrategies.set(serviceId, {
kind: "configured-override",
command: executable.configuredCommand,
verification: "unverified",
});
continue;
}
serviceIdsToProbe.push(serviceId);
selectionRequirements.push(commandRequirement(serviceId, executable.namedCommand));
}
let outcomes = new Map<string, NativeServicePrerequisiteOutcome>();
if (selectionRequirements.length > 0) {
const probeResult = await runSelectionProbe(input, selectionRequirements, dependencies.probe);
if (probeResult.kind === "infrastructure-failure") {
return {
ok: false,
failures: [{ kind: "probe-infrastructure", serviceIds: serviceIdsToProbe, message: probeResult.message }],
};
}
const parsedOutcomes = probeOutcomes(selectionRequirements, probeResult.outcomes);
if (parsedOutcomes.kind === "infrastructure-failure") {
return {
ok: false,
failures: [{ kind: "probe-infrastructure", serviceIds: serviceIdsToProbe, message: parsedOutcomes.message }],
};
}
outcomes = parsedOutcomes.outcomes;
}
const strategies = new Map(configuredStrategies);
const failures: NativeServicePlanFailure[] = [];
for (const serviceId of serviceIdsToProbe) {
const executable = input.executables[serviceId];
const outcome = outcomes.get(commandRequirementId(serviceId, executable.namedCommand));
if (outcome?.status === "satisfied") {
strategies.set(serviceId, {
kind: "named-command",
command: executable.namedCommand,
selectedBy: "authoritative-backend-probe",
});
continue;
}
let entrypointExists: boolean;
try {
entrypointExists = dependencies.fileExists(executable.bundledEntrypointPath);
} catch (error: unknown) {
failures.push({
kind: "entrypoint-inspection-failure",
serviceId,
entrypointPath: executable.bundledEntrypointPath,
message: errorMessage(error),
});
continue;
}
if (entrypointExists) {
strategies.set(serviceId, {
kind: "bundled-entrypoint",
command: "node",
entrypointPath: executable.bundledEntrypointPath,
namedCommand: executable.namedCommand,
namedCommandFailure: outcome?.detail ?? null,
});
continue;
}
failures.push({
kind: "executable-unavailable",
serviceId,
namedCommand: executable.namedCommand,
namedCommandFailure: outcome?.detail ?? null,
bundledEntrypointPath: executable.bundledEntrypointPath,
});
}
if (failures.length > 0) return { ok: false, failures };
return {
ok: true,
plan: {
mode: "production",
backend: input.backend,
shell: input.shell,
services: productionServiceIds.map((serviceId) => productionService(input, serviceId, requiredStrategy(strategies, serviceId))),
},
};
}
export function createDevelopmentNativeServicePlan(input: DevelopmentNativeServicePlanInput): NativeServicePlan {
const environment = copyEnvironment(input.environment);
const sessiondScripts = ["start:sessiond"] as const;
const uiDevScripts = ["dev:web", "dev:client"] as const;
const uiDevCommand = 'trap "kill 0" EXIT; npm run dev:web & npm run dev:client & wait';
return {
mode: "development",
backend: input.backend,
shell: input.shell,
services: [
{
id: "sessiond",
manager: nativeServiceRefs.sessiond,
description: "PI WEB session daemon (dev)",
shellCommand: "exec npm run start:sessiond",
strategy: { kind: "development-npm-script", script: "start:sessiond" },
restart: "never",
environment,
workingDirectory: input.workingDirectory,
after: [],
wants: [],
prerequisites: [
nodeRequirement("sessiond"),
commandRequirement("sessiond", "npm"),
packageScriptsRequirement("sessiond", input.packageJsonPath, sessiondScripts),
],
},
{
id: "uiDev",
manager: nativeServiceRefs.uiDev,
description: "PI WEB UI dev server",
shellCommand: `exec /usr/bin/env bash -c ${shellSingleQuote(input.shell.name, uiDevCommand)}`,
strategy: { kind: "development-npm-script-group", scripts: uiDevScripts, interpreter: "bash" },
restart: "never",
environment,
workingDirectory: input.workingDirectory,
after: ["sessiond"],
wants: ["sessiond"],
prerequisites: [
nodeRequirement("uiDev"),
commandRequirement("uiDev", "npm"),
commandRequirement("uiDev", "bash"),
packageScriptsRequirement("uiDev", input.packageJsonPath, uiDevScripts),
],
},
],
};
}
export function planValidationProbeRequests(plan: NativeServicePlan): readonly NativeServiceProbeRequest[] {
return plan.services.flatMap((service) => service.prerequisites.length === 0 ? [] : [{
purpose: "plan-validation" as const,
backend: plan.backend,
shell: plan.shell,
environment: service.environment,
workingDirectory: service.workingDirectory,
prerequisites: service.prerequisites,
}]);
}
function productionService(
input: ProductionNativeServicePlanInput,
serviceId: ProductionNativeServiceId,
strategy: NativeServiceCommandStrategy,
): NativeServicePlanService {
const isWeb = serviceId === "web";
return {
id: serviceId,
manager: nativeServiceRefs[serviceId],
description: isWeb ? "PI WEB server" : "PI WEB session daemon",
shellCommand: `exec ${strategyCommand(input.shell, strategy)}`,
strategy,
restart: "on-failure",
environment: copyEnvironment(input.environment),
workingDirectory: null,
after: isWeb ? ["sessiond"] : [],
wants: isWeb ? ["sessiond"] : [],
prerequisites: strategyPrerequisites(serviceId, strategy),
};
}
function strategyCommand(shell: NativeServiceShell, strategy: NativeServiceCommandStrategy): string {
switch (strategy.kind) {
case "configured-override":
case "named-command":
return strategy.command;
case "bundled-entrypoint":
return `${strategy.command} ${shellSingleQuote(shell.name, strategy.entrypointPath)}`;
case "development-npm-script":
return `npm run ${strategy.script}`;
case "development-npm-script-group":
throw new Error("Development script groups define their complete service shell command");
}
}
function strategyPrerequisites(serviceId: ProductionNativeServiceId, strategy: NativeServiceCommandStrategy): readonly NativeServicePrerequisite[] {
switch (strategy.kind) {
case "configured-override":
return [];
case "named-command":
return [commandRequirement(serviceId, strategy.command), nodeRequirement(serviceId)];
case "bundled-entrypoint":
return [nodeRequirement(serviceId), readableFileRequirement(serviceId, strategy.entrypointPath)];
case "development-npm-script":
case "development-npm-script-group":
throw new Error(`Unexpected ${strategy.kind} strategy in a production plan`);
}
}
async function runSelectionProbe(
input: ProductionNativeServicePlanInput,
prerequisites: readonly NativeServicePrerequisite[],
probe: NativeServiceAuthoritativeProbe,
): Promise<NativeServiceProbeResult> {
try {
return await probe.run({
purpose: "executable-selection",
backend: input.backend,
shell: input.shell,
environment: copyEnvironment(input.environment),
workingDirectory: null,
prerequisites,
});
} catch (error: unknown) {
return { kind: "infrastructure-failure", message: errorMessage(error) };
}
}
function probeOutcomes(
prerequisites: readonly NativeServicePrerequisite[],
outcomes: readonly NativeServicePrerequisiteOutcome[],
): { kind: "completed"; outcomes: Map<string, NativeServicePrerequisiteOutcome> } | { kind: "infrastructure-failure"; message: string } {
const expectedIds = new Set(prerequisites.map((prerequisite) => prerequisite.id));
const byId = new Map<string, NativeServicePrerequisiteOutcome>();
for (const outcome of outcomes) {
if (!expectedIds.has(outcome.prerequisiteId)) {
return { kind: "infrastructure-failure", message: `Authoritative probe returned unexpected outcome ${outcome.prerequisiteId}.` };
}
if (byId.has(outcome.prerequisiteId)) {
return { kind: "infrastructure-failure", message: `Authoritative probe returned duplicate outcome ${outcome.prerequisiteId}.` };
}
byId.set(outcome.prerequisiteId, outcome);
}
const missing = prerequisites.find((prerequisite) => !byId.has(prerequisite.id));
if (missing !== undefined) {
return { kind: "infrastructure-failure", message: `Authoritative probe returned no outcome for ${missing.id}.` };
}
return { kind: "completed", outcomes: byId };
}
function requiredStrategy(
strategies: ReadonlyMap<ProductionNativeServiceId, NativeServiceCommandStrategy>,
serviceId: ProductionNativeServiceId,
): NativeServiceCommandStrategy {
const strategy = strategies.get(serviceId);
if (strategy === undefined) throw new Error(`Missing executable strategy for ${serviceId}`);
return strategy;
}
function hasConfiguredCommand(command: string | undefined): command is string {
return command !== undefined && command.trim() !== "";
}
function commandRequirementId(serviceId: NativeServiceId, command: string): string {
return `${serviceId}.command.${command}`;
}
function commandRequirement(serviceId: NativeServiceId, command: string): NativeServicePrerequisite {
return {
id: commandRequirementId(serviceId, command),
kind: "command-available",
command,
description: `${command} is available to the service shell`,
};
}
function nodeRequirement(serviceId: NativeServiceId): NativeServicePrerequisite {
return {
id: `${serviceId}.node`,
kind: "node-version",
command: "node",
minimumMajor: 22,
description: "node >= 22 is available to the service shell",
};
}
function readableFileRequirement(serviceId: NativeServiceId, path: string): NativeServicePrerequisite {
return {
id: `${serviceId}.entrypoint`,
kind: "readable-file",
path,
description: `bundled entrypoint is readable: ${path}`,
};
}
function packageScriptsRequirement(
serviceId: NativeServiceId,
packageJsonPath: string,
scripts: readonly string[],
): NativeServicePrerequisite {
return {
id: `${serviceId}.package-scripts`,
kind: "package-scripts",
packageJsonPath,
scripts,
description: `package.json defines scripts: ${scripts.join(", ")}`,
};
}
function shellSingleQuote(shell: NativeServiceShellName, value: string): string {
if (shell === "fish") return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
return `'${value.replaceAll("'", "'\\''")}'`;
}
function copyEnvironment(environment: Readonly<Record<string, string>>): Readonly<Record<string, string>> {
return { ...environment };
}
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}