mirror of
https://github.com/morluto/rea.git
synced 2026-11-13 18:22:42 +00:00
176 lines
6.8 KiB
TypeScript
176 lines
6.8 KiB
TypeScript
import {
|
|
copyFile,
|
|
mkdir,
|
|
readFile,
|
|
symlink,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { delimiter, join, resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
import { expect } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { processTest } from "../../support/process/processFixture.js";
|
|
|
|
// These prerequisites isolate npm selection from registry availability, MCP
|
|
// conformance and updater behavior, which have their own integration suites.
|
|
const updaterProgram = `
|
|
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
export async function runUpdate(previousVersion, host) {
|
|
await writeFile(join(host.root, "package.json"), JSON.stringify({ version: "6.9.0" }));
|
|
return { status: "updated", previousVersion, latestVersion: "6.9.0", installedVersion: "6.9.0", command: ["npm", "install", "rea-agents@6.9.0"] };
|
|
}
|
|
`;
|
|
const prerequisiteProgram = `
|
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
export const readNpmRelease = async () => ({ status: "published", taggedVersion: "6.9.0", stableVersion: "6.9.0" });
|
|
export const stableUpdateVersion = () => "6.9.0";
|
|
export const verifyInstalledCanaryPackage = async () => ({ controlled_protocol_prerequisite: true });
|
|
export async function installCanaryPackage(specifier, workspace) {
|
|
const root = join(workspace, "controlled-updater");
|
|
await mkdir(join(root, "dist/application"), { recursive: true });
|
|
await writeFile(join(root, "package.json"), '{"type":"module"}');
|
|
await writeFile(join(root, "dist/application/Update.js"), ${JSON.stringify(updaterProgram)});
|
|
await writeFile(join(root, "dist/application/UpdateRuntime.js"), 'export const systemUpdateHost = root => ({ root });');
|
|
return root;
|
|
}
|
|
`;
|
|
|
|
const npmProgram = (entry: string) => `#!${process.execPath}
|
|
import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
const args = process.argv.slice(2);
|
|
appendFileSync(process.env.REA_RELEASE_NPM_LOG, JSON.stringify({ entry: ${JSON.stringify(entry)}, arguments: args, cwd: process.cwd(), marker: process.env.REA_RELEASE_NPM_MARKER }) + "\\n");
|
|
if (args[0] === "view") process.stdout.write(JSON.stringify(["6.8.0"]));
|
|
else if (args[0] === "install") {
|
|
const prefix = args[args.indexOf("--prefix") + 1];
|
|
const root = join(prefix, "lib/node_modules/rea-agents");
|
|
mkdirSync(root, { recursive: true });
|
|
writeFileSync(join(root, "package.json"), JSON.stringify({ version: "6.8.0" }));
|
|
mkdirSync(join(prefix, "bin"), { recursive: true });
|
|
writeFileSync(join(prefix, "bin/rea-agents"), ${JSON.stringify(`#!${process.execPath}
|
|
process.stdout.write(" update Update REA\\n");
|
|
`)}, { mode: 0o700 });
|
|
} else if (args[0] === "root") process.stdout.write(join(process.env.npm_config_prefix, "lib/node_modules"));
|
|
else throw new Error("Unexpected npm invocation: " + JSON.stringify(args));
|
|
`;
|
|
|
|
const prepare = async () => {
|
|
const root = await createTestTempDirectory("rea-published-npm-selection-");
|
|
const controller = join(root, "controller with spaces");
|
|
const bin = join(root, "bin");
|
|
await mkdir(join(controller, "scripts"), { recursive: true });
|
|
await mkdir(bin);
|
|
await writeFile(join(controller, "package.json"), '{"type":"module"}');
|
|
await copyFile(
|
|
"scripts/verify-published-package.mjs",
|
|
join(controller, "scripts/verify-published-package.mjs"),
|
|
);
|
|
await symlink(resolve("node_modules"), join(controller, "node_modules"));
|
|
const prerequisites = join(root, "prerequisites.mjs");
|
|
await writeFile(prerequisites, prerequisiteProgram);
|
|
const replacements = new Map([
|
|
["./lib/release-package-canary.mjs", pathToFileURL(prerequisites).href],
|
|
["./ci/npm-release.mjs", pathToFileURL(prerequisites).href],
|
|
...[
|
|
"verify-package-commands.mjs",
|
|
"verifier-cleanup.mjs",
|
|
"verifier-run.mjs",
|
|
].map(
|
|
(name) =>
|
|
[
|
|
`./lib/${name}`,
|
|
pathToFileURL(resolve("scripts/lib", name)).href,
|
|
] as const,
|
|
),
|
|
]);
|
|
const hook = join(root, "selected-dependencies.mjs");
|
|
await writeFile(
|
|
hook,
|
|
`import { registerHooks } from "node:module";
|
|
const selected = new Map(${JSON.stringify([...replacements])});
|
|
const owner = ${JSON.stringify(pathToFileURL(join(controller, "scripts/verify-published-package.mjs")).href)};
|
|
registerHooks({ resolve(specifier, context, next) {
|
|
return next(context.parentURL === owner ? selected.get(specifier) ?? specifier : specifier, context);
|
|
} });
|
|
`,
|
|
);
|
|
const selectedNpm = join(root, "selected npm entry.mjs");
|
|
await writeFile(selectedNpm, npmProgram("selected"));
|
|
await writeFile(join(bin, "npm"), npmProgram("PATH"), { mode: 0o700 });
|
|
return { root, controller, hook, selectedNpm, bin };
|
|
};
|
|
|
|
processTest.skipIf(process.platform === "win32").for([true, false])(
|
|
"preserves npm selection for all published-update operations (explicit selection: %s)",
|
|
async (selected, { processes }) => {
|
|
const { root, controller, hook, selectedNpm, bin } = await prepare();
|
|
const log = join(root, "npm-operations.jsonl");
|
|
const env = {
|
|
...process.env,
|
|
PATH: `${bin}${delimiter}${process.env.PATH ?? ""}`,
|
|
npm_execpath: selected ? selectedNpm : undefined,
|
|
REA_RELEASE_NPM_LOG: log,
|
|
REA_RELEASE_NPM_MARKER: "caller selection with spaces",
|
|
};
|
|
const result = await processes.run(
|
|
process.execPath,
|
|
["--import", hook, "scripts/verify-published-package.mjs", "6.9.0"],
|
|
{ cwd: controller, env, timeoutMs: 20_000 },
|
|
);
|
|
expect(result.exitCode, result.stderr).toBe(0);
|
|
const calls = z
|
|
.array(
|
|
z.object({
|
|
entry: z.string(),
|
|
arguments: z.array(z.string()),
|
|
cwd: z.string(),
|
|
marker: z.string(),
|
|
}),
|
|
)
|
|
.parse(
|
|
(await readFile(log, "utf8"))
|
|
.trim()
|
|
.split("\n")
|
|
.map((line) => JSON.parse(line)),
|
|
);
|
|
expect(calls).toHaveLength(3);
|
|
expect(calls.map(({ entry }) => entry)).toEqual(
|
|
Array(3).fill(selected ? "selected" : "PATH"),
|
|
);
|
|
expect(calls.map(({ marker }) => marker)).toEqual(
|
|
Array(3).fill("caller selection with spaces"),
|
|
);
|
|
const workspace = calls[0]!.cwd;
|
|
expect(calls.map(({ cwd }) => cwd)).toEqual([
|
|
workspace,
|
|
workspace,
|
|
workspace,
|
|
]);
|
|
expect(calls.map(({ arguments: args }) => args)).toEqual([
|
|
["view", "rea-agents", "versions", "--json"],
|
|
[
|
|
"install",
|
|
"--global",
|
|
"--ignore-scripts",
|
|
"--prefix",
|
|
join(workspace, "update-prefix"),
|
|
"rea-agents@6.8.0",
|
|
],
|
|
["root", "--global"],
|
|
]);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
version: "6.9.0",
|
|
update: {
|
|
status: "updated",
|
|
previousVersion: "6.8.0",
|
|
installedVersion: "6.9.0",
|
|
},
|
|
});
|
|
},
|
|
);
|