mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
588 lines
22 KiB
JavaScript
588 lines
22 KiB
JavaScript
import { execFile, spawn } from "node:child_process";
|
|
import { mkdir, mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
test("the public contract contains no host or provider authority fields", async () => {
|
|
const source = await readFile(new URL("../src/index.ts", import.meta.url), "utf8");
|
|
assert.doesNotMatch(source, /host-directory|hostVisiblePath|provider-probe|provider:/u);
|
|
});
|
|
import { promisify } from "node:util";
|
|
|
|
import {
|
|
CommandRuntimeControlSchema,
|
|
CommandRuntimeDescribeResponseSchema,
|
|
CommandRuntimeLifecycleRequestSchema,
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
CommandRuntimeProcessEventSchema,
|
|
CommandRuntimeProjectSourceSchema,
|
|
CommandRuntimePlacementIntentSchema,
|
|
CommandRuntimeCreateInputSchema,
|
|
CommandRuntimeInspectionSchema,
|
|
CommandRuntimePlacementSchema,
|
|
CommandRuntimeProcessPurposeSchema,
|
|
CommandRuntimeSpawnEnvelopeSchema,
|
|
CommandRuntimeStdioSchema,
|
|
CommandRuntimeStateSchema,
|
|
createCommandRuntimeMessageDecoder,
|
|
createCommandRuntimeProcessEventDecoder,
|
|
encodeCommandRuntimeMessage,
|
|
} from "../dist/index.js";
|
|
|
|
const run = promisify(execFile);
|
|
const npmCommand = process.platform === "win32" ? process.execPath : "npm";
|
|
const npmPrefixArgs =
|
|
process.platform === "win32" ? [requireEnvironmentVariable("npm_execpath")] : [];
|
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const examples = JSON.parse(await readFile(path.join(packageRoot, "examples/v1.json"), "utf8"));
|
|
|
|
function requireEnvironmentVariable(name) {
|
|
const value = process.env[name];
|
|
if (!value) throw new Error(`${name} is required to invoke npm on Windows`);
|
|
return value;
|
|
}
|
|
|
|
function runNpm(args, options) {
|
|
return run(npmCommand, [...npmPrefixArgs, ...args], options);
|
|
}
|
|
|
|
test("documented lifecycle, fd3, control, and fd4 examples are exact newline-terminated bytes", () => {
|
|
for (const [name, schema] of [
|
|
["describeResponse", CommandRuntimeDescribeResponseSchema],
|
|
["lifecycleRequest", CommandRuntimeLifecycleRequestSchema],
|
|
["lifecycleResponse", CommandRuntimeLifecycleResponseSchema],
|
|
["pipeSpawnControl", CommandRuntimeControlSchema],
|
|
["ptySpawnControl", CommandRuntimeControlSchema],
|
|
["resizeControl", CommandRuntimeControlSchema],
|
|
["signalControl", CommandRuntimeControlSchema],
|
|
["startedEvent", CommandRuntimeProcessEventSchema],
|
|
["eofEvent", CommandRuntimeProcessEventSchema],
|
|
["resizedEvent", CommandRuntimeProcessEventSchema],
|
|
["exitEvent", CommandRuntimeProcessEventSchema],
|
|
["errorEvent", CommandRuntimeProcessEventSchema],
|
|
]) {
|
|
const bytes = encodeCommandRuntimeMessage(schema, examples[name]);
|
|
assert.equal(bytes, examples.bytes[name]);
|
|
assert.equal(bytes.endsWith("\n"), true);
|
|
assert.equal(bytes.endsWith("\n\n"), false);
|
|
}
|
|
});
|
|
|
|
test("lifecycle uses one exact stdin request and one exact stdout response", async () => {
|
|
assert.equal(examples.lifecycleRequest.input.purpose, "discovery");
|
|
assert.deepEqual(examples.lifecycleResponse.state, {
|
|
workspaceId: "workspace-01",
|
|
lifecycle: "ready",
|
|
});
|
|
assert.equal(examples.lifecycleResponse.materializedFreshContent, true);
|
|
assert.doesNotMatch(examples.bytes.lifecycleResponse, /root|revision|executionDomainId/);
|
|
const child = spawn(
|
|
process.execPath,
|
|
[path.join(packageRoot, "test/pipe-fixture.mjs"), "lifecycle"],
|
|
{
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
},
|
|
);
|
|
child.stdin.end(examples.bytes.lifecycleRequest);
|
|
const [stdout, stderr, exit] = await Promise.all([
|
|
collect(child.stdout),
|
|
collect(child.stderr),
|
|
exited(child),
|
|
]);
|
|
assert.deepEqual(exit, { code: 0, signal: null });
|
|
assert.equal(stdout.toString(), examples.bytes.lifecycleResponse);
|
|
assert.equal(stderr.length, 0);
|
|
});
|
|
|
|
test("pipes use one fd3 spawn then started/eof/exit on fd4 with raw fd0/1/2", async () => {
|
|
const child = spawn(process.execPath, [path.join(packageRoot, "test/pipe-fixture.mjs")], {
|
|
stdio: ["pipe", "pipe", "pipe", "pipe", "pipe"],
|
|
});
|
|
child.stdin.end(Buffer.from("raw\u0000stdin\n"));
|
|
child.stdio[3].end(examples.bytes.pipeSpawnControl);
|
|
const [stdout, stderr, events, exit] = await Promise.all([
|
|
collect(child.stdout),
|
|
collect(child.stderr),
|
|
collect(child.stdio[4]),
|
|
exited(child),
|
|
]);
|
|
assert.deepEqual(exit, { code: 0, signal: null });
|
|
assert.deepEqual(stdout, Buffer.from([0x00, 0x6f, 0x75, 0x74, 0xff]));
|
|
assert.deepEqual(stderr, Buffer.from([0x65, 0x72, 0x72, 0x00]));
|
|
assert.equal(
|
|
events.toString(),
|
|
examples.bytes.startedEvent + examples.bytes.eofEvent + examples.bytes.exitEvent,
|
|
);
|
|
assert.deepEqual(decodeEvents(events), [
|
|
examples.startedEvent,
|
|
examples.eofEvent,
|
|
examples.exitEvent,
|
|
]);
|
|
});
|
|
|
|
test("PTY keeps fd3 open for resize and signal through EOF and emits a valid fd4 sequence", async () => {
|
|
const child = spawn(process.execPath, [path.join(packageRoot, "test/pty-fixture.mjs")], {
|
|
stdio: ["pipe", "pipe", "pipe", "pipe", "pipe"],
|
|
});
|
|
child.stdin.end(Buffer.from([0x74, 0x65, 0x72, 0x6d, 0x00, 0xff]));
|
|
child.stdio[3].write(examples.bytes.ptySpawnControl);
|
|
child.stdio[3].write(examples.bytes.resizeControl);
|
|
child.stdio[3].end(examples.bytes.signalControl);
|
|
const [stdout, stderr, events, exit] = await Promise.all([
|
|
collect(child.stdout),
|
|
collect(child.stderr),
|
|
collect(child.stdio[4]),
|
|
exited(child),
|
|
]);
|
|
assert.deepEqual(exit, { code: 0, signal: null });
|
|
assert.deepEqual(stdout, Buffer.from([0x74, 0x65, 0x72, 0x6d, 0x00, 0xff]));
|
|
assert.deepEqual(stderr, Buffer.from([0x70, 0x74, 0x79, 0x00]));
|
|
assert.equal(
|
|
events.toString(),
|
|
examples.bytes.startedEvent +
|
|
examples.bytes.resizedEvent +
|
|
examples.bytes.eofEvent +
|
|
'{"type":"exit","protocolVersion":1,"code":null,"signal":"SIGTERM"}\n',
|
|
);
|
|
assert.deepEqual(
|
|
decodeEvents(events).map((event) => event.type),
|
|
["started", "resized", "eof", "exit"],
|
|
);
|
|
});
|
|
|
|
test("actual fd4 rejects malformed and wrong-version frames and detects early wrapper exit", async () => {
|
|
for (const [mode, pattern] of [
|
|
["malformed", SyntaxError],
|
|
["wrong-version", /expected 1/],
|
|
]) {
|
|
const child = spawn(process.execPath, [path.join(packageRoot, "test/pipe-fixture.mjs"), mode], {
|
|
stdio: ["ignore", "ignore", "ignore", "ignore", "pipe"],
|
|
});
|
|
const [events, exit] = await Promise.all([collect(child.stdio[4]), exited(child)]);
|
|
assert.deepEqual(exit, { code: 0, signal: null });
|
|
assert.throws(() => decodeEvents(events), pattern);
|
|
}
|
|
|
|
const early = spawn(
|
|
process.execPath,
|
|
[path.join(packageRoot, "test/pipe-fixture.mjs"), "early-exit"],
|
|
{ stdio: ["ignore", "ignore", "ignore", "ignore", "pipe"] },
|
|
);
|
|
const [events, exit] = await Promise.all([collect(early.stdio[4]), exited(early)]);
|
|
assert.equal(events.length, 0);
|
|
assert.deepEqual(exit, { code: 47, signal: null });
|
|
});
|
|
|
|
test("framing accepts partial bytes and rejects malformed, early EOF, and wrong versions", () => {
|
|
const partial = createCommandRuntimeMessageDecoder(CommandRuntimeProcessEventSchema);
|
|
assert.deepEqual(partial.push(examples.bytes.exitEvent.slice(0, 11)), []);
|
|
assert.deepEqual(partial.push(examples.bytes.exitEvent.slice(11)), [examples.exitEvent]);
|
|
partial.finish();
|
|
|
|
const malformed = createCommandRuntimeMessageDecoder(CommandRuntimeProcessEventSchema);
|
|
assert.throws(() => malformed.push("{nope}\n"), SyntaxError);
|
|
|
|
const early = createCommandRuntimeMessageDecoder(CommandRuntimeProcessEventSchema);
|
|
early.push(examples.bytes.exitEvent.slice(0, -1));
|
|
assert.throws(() => early.finish(), /ended before newline/);
|
|
|
|
const wrongVersion = createCommandRuntimeMessageDecoder(CommandRuntimeProcessEventSchema);
|
|
assert.throws(
|
|
() => wrongVersion.push('{"type":"exit","protocolVersion":2,"code":0,"signal":null}\n'),
|
|
/expected 1/,
|
|
);
|
|
});
|
|
|
|
test("process event conversations enforce the complete mode-specific lifecycle", () => {
|
|
for (const [mode, events] of [
|
|
["pipes", [examples.startedEvent, examples.eofEvent, examples.exitEvent]],
|
|
["pty", [examples.startedEvent, examples.resizedEvent, examples.eofEvent, examples.exitEvent]],
|
|
]) {
|
|
const decoder = createCommandRuntimeProcessEventDecoder(mode);
|
|
assert.deepEqual(
|
|
decoder.push(
|
|
events
|
|
.map((event) => encodeCommandRuntimeMessage(CommandRuntimeProcessEventSchema, event))
|
|
.join(""),
|
|
),
|
|
events,
|
|
);
|
|
decoder.finish();
|
|
}
|
|
|
|
for (const [name, mode, events, pattern] of [
|
|
["exit before eof", "pipes", [examples.startedEvent, examples.exitEvent], /exit before eof/],
|
|
["eof before started", "pipes", [examples.eofEvent], /eof before started/],
|
|
["exit before started", "pipes", [examples.exitEvent], /exit before started/],
|
|
[
|
|
"duplicate started",
|
|
"pipes",
|
|
[examples.startedEvent, examples.startedEvent],
|
|
/duplicate started/,
|
|
],
|
|
[
|
|
"duplicate eof",
|
|
"pipes",
|
|
[examples.startedEvent, examples.eofEvent, examples.eofEvent],
|
|
/duplicate eof/,
|
|
],
|
|
[
|
|
"duplicate exit",
|
|
"pipes",
|
|
[examples.startedEvent, examples.eofEvent, examples.exitEvent, examples.exitEvent],
|
|
/duplicate exit/,
|
|
],
|
|
[
|
|
"post-exit lifecycle",
|
|
"pipes",
|
|
[examples.startedEvent, examples.eofEvent, examples.exitEvent, examples.eofEvent],
|
|
/event after exit/,
|
|
],
|
|
[
|
|
"wrong-mode resize",
|
|
"pipes",
|
|
[examples.startedEvent, examples.resizedEvent],
|
|
/pipes.*resized/,
|
|
],
|
|
["PTY resize before started", "pty", [examples.resizedEvent], /resized before started/],
|
|
[
|
|
"PTY resize after eof",
|
|
"pty",
|
|
[examples.startedEvent, examples.eofEvent, examples.resizedEvent],
|
|
/resized after eof/,
|
|
],
|
|
[
|
|
"PTY post-exit acknowledgement",
|
|
"pty",
|
|
[examples.startedEvent, examples.eofEvent, examples.exitEvent, examples.resizedEvent],
|
|
/event after exit/,
|
|
],
|
|
]) {
|
|
const decoder = createCommandRuntimeProcessEventDecoder(mode);
|
|
assert.throws(
|
|
() =>
|
|
decoder.push(
|
|
events
|
|
.map((event) => encodeCommandRuntimeMessage(CommandRuntimeProcessEventSchema, event))
|
|
.join(""),
|
|
),
|
|
pattern,
|
|
name,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("every public v1 object rejects unknown authority at every nesting level", () => {
|
|
const createInput = examples.lifecycleRequest.input;
|
|
const gitSource = {
|
|
kind: "git",
|
|
url: "https://example.invalid/repository.git",
|
|
revision: "main",
|
|
subdirectory: "project",
|
|
};
|
|
const resolvedPlacement = {
|
|
kind: "resolved-worktree",
|
|
worktreeSlug: "change-7",
|
|
source: {
|
|
kind: "checkout-change-request",
|
|
headRef: "feature",
|
|
baseRefName: "main",
|
|
checkoutRefs: [{ remoteName: "origin", remoteRef: "refs/heads/feature" }],
|
|
},
|
|
};
|
|
const readyInspection = {
|
|
status: "ready",
|
|
state: examples.lifecycleResponse.state,
|
|
placement: examples.lifecycleResponse.placement,
|
|
};
|
|
const pausedInspection = {
|
|
status: "paused",
|
|
state: { ...examples.lifecycleResponse.state, lifecycle: "paused" },
|
|
placement: examples.lifecycleResponse.placement,
|
|
};
|
|
const strictCases = [
|
|
["describe", CommandRuntimeDescribeResponseSchema, examples.describeResponse, []],
|
|
["create request", CommandRuntimeLifecycleRequestSchema, examples.lifecycleRequest, []],
|
|
[
|
|
"create request input",
|
|
CommandRuntimeLifecycleRequestSchema,
|
|
examples.lifecycleRequest,
|
|
["input"],
|
|
],
|
|
[
|
|
"inspect request",
|
|
CommandRuntimeLifecycleRequestSchema,
|
|
{ protocolVersion: 1, options: {} },
|
|
[],
|
|
],
|
|
[
|
|
"pause request",
|
|
CommandRuntimeLifecycleRequestSchema,
|
|
{ protocolVersion: 1, options: {} },
|
|
[],
|
|
],
|
|
[
|
|
"resume request",
|
|
CommandRuntimeLifecycleRequestSchema,
|
|
{ protocolVersion: 1, options: {} },
|
|
[],
|
|
],
|
|
[
|
|
"destroy request",
|
|
CommandRuntimeLifecycleRequestSchema,
|
|
{ protocolVersion: 1, options: {} },
|
|
[],
|
|
],
|
|
["create input", CommandRuntimeCreateInputSchema, createInput, []],
|
|
["create project", CommandRuntimeCreateInputSchema, createInput, ["project"]],
|
|
["create source", CommandRuntimeCreateInputSchema, createInput, ["project", "source"]],
|
|
["create placement", CommandRuntimeCreateInputSchema, createInput, ["placement"]],
|
|
["host source", CommandRuntimeProjectSourceSchema, createInput.project.source, []],
|
|
["git source", CommandRuntimeProjectSourceSchema, gitSource, []],
|
|
["existing placement", CommandRuntimePlacementIntentSchema, createInput.placement, []],
|
|
[
|
|
"branch placement",
|
|
CommandRuntimePlacementIntentSchema,
|
|
{ kind: "branch", branchName: "feature", baseRef: "main" },
|
|
[],
|
|
],
|
|
[
|
|
"checkout placement",
|
|
CommandRuntimePlacementIntentSchema,
|
|
{ kind: "checkout", ref: "feature" },
|
|
[],
|
|
],
|
|
["resolved placement", CommandRuntimePlacementIntentSchema, resolvedPlacement, []],
|
|
["resolved source", CommandRuntimePlacementIntentSchema, resolvedPlacement, ["source"]],
|
|
[
|
|
"resolved checkout ref",
|
|
CommandRuntimePlacementIntentSchema,
|
|
resolvedPlacement,
|
|
["source", "checkoutRefs", 0],
|
|
],
|
|
["state", CommandRuntimeStateSchema, examples.lifecycleResponse.state, []],
|
|
["placement", CommandRuntimePlacementSchema, examples.lifecycleResponse.placement, []],
|
|
[
|
|
"create state response",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
examples.lifecycleResponse,
|
|
[],
|
|
],
|
|
[
|
|
"resume state response",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
examples.lifecycleResponse,
|
|
[],
|
|
],
|
|
[
|
|
"state response state",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
examples.lifecycleResponse,
|
|
["state"],
|
|
],
|
|
[
|
|
"state response placement",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
examples.lifecycleResponse,
|
|
["placement"],
|
|
],
|
|
["ready inspection", CommandRuntimeInspectionSchema, readyInspection, []],
|
|
["ready inspection state", CommandRuntimeInspectionSchema, readyInspection, ["state"]],
|
|
["ready inspection placement", CommandRuntimeInspectionSchema, readyInspection, ["placement"]],
|
|
["paused inspection", CommandRuntimeInspectionSchema, pausedInspection, []],
|
|
["paused inspection state", CommandRuntimeInspectionSchema, pausedInspection, ["state"]],
|
|
[
|
|
"paused inspection placement",
|
|
CommandRuntimeInspectionSchema,
|
|
pausedInspection,
|
|
["placement"],
|
|
],
|
|
["missing inspection", CommandRuntimeInspectionSchema, { status: "missing" }, []],
|
|
[
|
|
"error inspection",
|
|
CommandRuntimeInspectionSchema,
|
|
{ status: "error", message: "failure" },
|
|
[],
|
|
],
|
|
[
|
|
"inspection response",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
{ protocolVersion: 1, type: "inspection", inspection: { status: "missing" } },
|
|
[],
|
|
],
|
|
[
|
|
"ready inspection response state",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
{ protocolVersion: 1, type: "inspection", inspection: readyInspection },
|
|
["inspection", "state"],
|
|
],
|
|
[
|
|
"ready inspection response placement",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
{ protocolVersion: 1, type: "inspection", inspection: readyInspection },
|
|
["inspection", "placement"],
|
|
],
|
|
[
|
|
"nested inspection",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
{ protocolVersion: 1, type: "inspection", inspection: { status: "missing" } },
|
|
["inspection"],
|
|
],
|
|
[
|
|
"pause ok response",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
{ protocolVersion: 1, type: "ok" },
|
|
[],
|
|
],
|
|
[
|
|
"destroy ok response",
|
|
CommandRuntimeLifecycleResponseSchema,
|
|
{ protocolVersion: 1, type: "ok" },
|
|
[],
|
|
],
|
|
["agent purpose", CommandRuntimeProcessPurposeSchema, { kind: "agent" }, []],
|
|
["terminal purpose", CommandRuntimeProcessPurposeSchema, { kind: "terminal" }, []],
|
|
["Git purpose", CommandRuntimeProcessPurposeSchema, { kind: "git" }, []],
|
|
["discovery purpose", CommandRuntimeProcessPurposeSchema, { kind: "discovery" }, []],
|
|
["helper purpose", CommandRuntimeProcessPurposeSchema, { kind: "workspace-helper" }, []],
|
|
["script purpose", CommandRuntimeProcessPurposeSchema, examples.pipeSpawnControl.purpose, []],
|
|
["setup purpose", CommandRuntimeProcessPurposeSchema, { kind: "setup" }, []],
|
|
["archive purpose", CommandRuntimeProcessPurposeSchema, { kind: "archive" }, []],
|
|
["pipes stdio", CommandRuntimeStdioSchema, { kind: "pipes" }, []],
|
|
["PTY stdio", CommandRuntimeStdioSchema, examples.ptySpawnControl.stdio, []],
|
|
["spawn", CommandRuntimeSpawnEnvelopeSchema, examples.pipeSpawnControl, []],
|
|
["spawn purpose", CommandRuntimeSpawnEnvelopeSchema, examples.pipeSpawnControl, ["purpose"]],
|
|
["spawn stdio", CommandRuntimeSpawnEnvelopeSchema, examples.pipeSpawnControl, ["stdio"]],
|
|
["resize control", CommandRuntimeControlSchema, examples.resizeControl, []],
|
|
["signal control", CommandRuntimeControlSchema, examples.signalControl, []],
|
|
["started event", CommandRuntimeProcessEventSchema, examples.startedEvent, []],
|
|
["eof event", CommandRuntimeProcessEventSchema, examples.eofEvent, []],
|
|
["resized event", CommandRuntimeProcessEventSchema, examples.resizedEvent, []],
|
|
["exit event", CommandRuntimeProcessEventSchema, examples.exitEvent, []],
|
|
["error event", CommandRuntimeProcessEventSchema, examples.errorEvent, []],
|
|
];
|
|
for (const [name, schema, value, nestingPath] of strictCases) {
|
|
for (const field of ["root", "unknownV1Field"]) {
|
|
assert.throws(
|
|
() => schema.parse(inject(value, nestingPath, field, "/private")),
|
|
/unrecognized key/i,
|
|
`${name} accepted ${field}`,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("v1 extension maps stay explicit and future versions fail by protocol version", () => {
|
|
assert.deepEqual(
|
|
CommandRuntimeLifecycleRequestSchema.parse({
|
|
protocolVersion: 1,
|
|
runtimeInstanceId: examples.lifecycleRequest.runtimeInstanceId,
|
|
options: { root: "runtime-private-option", vendorExtension: { nested: true } },
|
|
}).options,
|
|
{ root: "runtime-private-option", vendorExtension: { nested: true } },
|
|
);
|
|
assert.deepEqual(
|
|
CommandRuntimeStateSchema.parse({
|
|
...examples.lifecycleResponse.state,
|
|
lifecycleEnvironment: { root: "environment-value", VENDOR_VALUE: "accepted" },
|
|
}).lifecycleEnvironment,
|
|
{ root: "environment-value", VENDOR_VALUE: "accepted" },
|
|
);
|
|
assert.deepEqual(
|
|
CommandRuntimeSpawnEnvelopeSchema.parse({
|
|
...examples.pipeSpawnControl,
|
|
env: { root: "workload-environment-value" },
|
|
options: { vendorExtension: true },
|
|
}),
|
|
{
|
|
...examples.pipeSpawnControl,
|
|
env: { root: "workload-environment-value" },
|
|
options: { vendorExtension: true },
|
|
},
|
|
);
|
|
for (const [schema, value] of [
|
|
[CommandRuntimeDescribeResponseSchema, examples.describeResponse],
|
|
[CommandRuntimeLifecycleRequestSchema, examples.lifecycleRequest],
|
|
[CommandRuntimeLifecycleResponseSchema, examples.lifecycleResponse],
|
|
[CommandRuntimeControlSchema, examples.pipeSpawnControl],
|
|
[CommandRuntimeProcessEventSchema, examples.startedEvent],
|
|
]) {
|
|
assert.throws(() => schema.parse({ ...value, protocolVersion: 2 }), /expected 1/);
|
|
}
|
|
});
|
|
|
|
test("public state schemas reject physical authority under computed and aliased spellings", () => {
|
|
const root = ["ro", "ot"].join("");
|
|
const state = { workspaceId: "workspace-01", lifecycle: "ready", [root]: "/private" };
|
|
const schemaAlias = CommandRuntimeStateSchema;
|
|
assert.throws(() => schemaAlias.parse(state), /unrecognized key/i);
|
|
assert.throws(
|
|
() =>
|
|
CommandRuntimeLifecycleResponseSchema.parse({
|
|
...examples.lifecycleResponse,
|
|
state: { ...examples.lifecycleResponse.state, [root]: "/private" },
|
|
}),
|
|
/unrecognized key/i,
|
|
);
|
|
});
|
|
|
|
test("every export condition resolves from a packed standalone install", async (t) => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "workspace-runtime-contract-pack-"));
|
|
t.after(() => rm(root, { recursive: true, force: true }));
|
|
const pack = JSON.parse(
|
|
(
|
|
await runNpm(["pack", "--json", "--ignore-scripts", "--pack-destination", root], {
|
|
cwd: packageRoot,
|
|
})
|
|
).stdout,
|
|
);
|
|
const archive = path.join(root, pack[0].filename);
|
|
const installRoot = path.join(root, "standalone");
|
|
await mkdir(installRoot);
|
|
await runNpm(["install", "--ignore-scripts", "--no-package-lock", archive], {
|
|
cwd: installRoot,
|
|
});
|
|
const installed = path.join(installRoot, "node_modules/@getpaseo/workspace-runtime-contract");
|
|
const manifest = JSON.parse(await readFile(path.join(installed, "package.json"), "utf8"));
|
|
for (const target of Object.values(manifest.exports["."])) {
|
|
await stat(path.join(installed, target));
|
|
}
|
|
const imported = await import(pathToFileURL(path.join(installed, manifest.exports["."].default)));
|
|
assert.equal(imported.COMMAND_RUNTIME_PROTOCOL_VERSION, 1);
|
|
});
|
|
|
|
async function collect(stream) {
|
|
const chunks = [];
|
|
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
return Buffer.concat(chunks);
|
|
}
|
|
|
|
function exited(child) {
|
|
return new Promise((resolve, reject) => {
|
|
child.once("error", reject);
|
|
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
});
|
|
}
|
|
|
|
function decodeEvents(bytes) {
|
|
const decoder = createCommandRuntimeMessageDecoder(CommandRuntimeProcessEventSchema);
|
|
const split = Math.min(13, bytes.length);
|
|
const events = [
|
|
...decoder.push(bytes.subarray(0, split)),
|
|
...decoder.push(bytes.subarray(split)),
|
|
];
|
|
decoder.finish();
|
|
return events;
|
|
}
|
|
|
|
function inject(value, nestingPath, field, injected) {
|
|
const copy = structuredClone(value);
|
|
let target = copy;
|
|
for (const segment of nestingPath) target = target[segment];
|
|
target[field] = injected;
|
|
return copy;
|
|
}
|