mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 01:32:40 +00:00
* feat(plugins): enforce declared Paseo version requirements Treat manifests without a Paseo requirement as pre-0.8 plugins so the first breaking plugin release offers an explicit migration path. Check the daemon and app independently before plugin execution, and preserve installed Git revisions when updates are incompatible. * fix(nix): refresh dependency hash for plugin requirements * refactor(plugins): validate test manifest inputs and name compatibility input
172 lines
7.5 KiB
TypeScript
172 lines
7.5 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
import { resolveCliVersion } from "../../src/version.js";
|
|
import { readPluginManifest } from "../../../server/src/server/plugins/manifest.js";
|
|
|
|
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { promisify } from "node:util";
|
|
import { connectToDaemon } from "../../src/utils/client.ts";
|
|
import { createE2ETestContext } from "../helpers/test-daemon.ts";
|
|
|
|
const pluginSource = `export default function contribute(plugin: unknown) {
|
|
void plugin;
|
|
return () => undefined;
|
|
}`;
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
async function git(cwd: string, args: string[]): Promise<void> {
|
|
await execFileAsync("git", args, { cwd });
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const directory = await mkdtemp(path.join(tmpdir(), "paseo-plugin-cli-e2e-"));
|
|
const gitDirectory = await mkdtemp(path.join(tmpdir(), "paseo-plugin-git-cli-e2e-"));
|
|
const context = await createE2ETestContext({ timeout: 45_000 });
|
|
try {
|
|
const scaffold = path.join(context.workDir, "authored-plugin");
|
|
const init = await context.paseo(["plugin", "init", scaffold, "--json"]);
|
|
assert.equal(init.exitCode, 0, init.stderr);
|
|
const manifestPath = path.join(scaffold, "paseo-plugin.json");
|
|
const manifest = await readPluginManifest(scaffold);
|
|
assert.deepEqual(manifest.requirements, { paseo: `>=${resolveCliVersion()}` });
|
|
await writeFile(
|
|
path.join(directory, "paseo-plugin.json"),
|
|
JSON.stringify({ id: "cli-e2e", requirements: { paseo: `>=${resolveCliVersion()}` } }),
|
|
);
|
|
await writeFile(path.join(directory, "index.server.ts"), pluginSource);
|
|
|
|
const install = await context.paseo(["plugin", "install", directory, "--json"]);
|
|
assert.equal(install.exitCode, 0, install.stderr);
|
|
assert.equal(JSON.parse(install.stdout).id, "cli-e2e");
|
|
|
|
const client = await connectToDaemon({ host: `127.0.0.1:${context.port}` });
|
|
await client.patchDaemonConfig({ pluginsEnabled: true });
|
|
await client.close();
|
|
|
|
await writeFile(
|
|
manifestPath,
|
|
JSON.stringify({ ...manifest, requirements: { paseo: ">=999.0.0" } }),
|
|
);
|
|
const incompatibleInstall = await context.paseo(["plugin", "install", scaffold, "--json"]);
|
|
assert.equal(incompatibleInstall.exitCode, 1);
|
|
assert.match(incompatibleInstall.stderr, /requires Paseo >=999.0.0/);
|
|
const afterRejection = await context.paseo(["plugin", "ls", "--json"]);
|
|
assert.equal(afterRejection.exitCode, 0, afterRejection.stderr);
|
|
assert.deepEqual(
|
|
JSON.parse(afterRejection.stdout).map((plugin: { id: string }) => plugin.id),
|
|
["cli-e2e"],
|
|
);
|
|
await writeFile(manifestPath, JSON.stringify(manifest));
|
|
const scaffoldInstall = await context.paseo(["plugin", "install", scaffold, "--json"]);
|
|
assert.equal(scaffoldInstall.exitCode, 0, scaffoldInstall.stderr);
|
|
assert.equal(JSON.parse(scaffoldInstall.stdout).status, "running");
|
|
|
|
await git(gitDirectory, ["init", "-b", "main"]);
|
|
await git(gitDirectory, ["config", "user.name", "Paseo Tests"]);
|
|
await git(gitDirectory, ["config", "user.email", "paseo@example.test"]);
|
|
await writeFile(
|
|
path.join(gitDirectory, "paseo-plugin.json"),
|
|
JSON.stringify({ id: "git-cli-e2e", requirements: { paseo: `>=${resolveCliVersion()}` } }),
|
|
);
|
|
await writeFile(path.join(gitDirectory, "index.server.ts"), pluginSource);
|
|
await git(gitDirectory, ["add", "-A"]);
|
|
await git(gitDirectory, ["commit", "-m", "initial"]);
|
|
|
|
const gitInstall = await context.paseo([
|
|
"plugin",
|
|
"add",
|
|
pathToFileURL(gitDirectory).href,
|
|
"--json",
|
|
]);
|
|
assert.equal(gitInstall.exitCode, 0, gitInstall.stderr);
|
|
assert.equal(JSON.parse(gitInstall.stdout).source, "git");
|
|
|
|
await writeFile(
|
|
path.join(gitDirectory, "index.server.ts"),
|
|
`${pluginSource}\nconst updated = true;\n`,
|
|
);
|
|
await git(gitDirectory, ["add", "-A"]);
|
|
await git(gitDirectory, ["commit", "-m", "update"]);
|
|
const status = await context.paseo(["plugin", "status", "git-cli-e2e", "--json"]);
|
|
assert.equal(status.exitCode, 0, status.stderr);
|
|
assert.equal(JSON.parse(status.stdout)[0].status, "running");
|
|
assert.equal(JSON.parse(status.stdout)[0].commit, JSON.parse(gitInstall.stdout).commit);
|
|
|
|
const update = await context.paseo(["plugin", "update", "git-cli-e2e", "--json"]);
|
|
assert.equal(update.exitCode, 0, update.stderr);
|
|
assert.equal(JSON.parse(update.stdout)[0].updated, true);
|
|
|
|
const installedCommit = JSON.parse(update.stdout)[0].currentCommit;
|
|
const buildMarker = path.join(context.workDir, "incompatible-build-ran");
|
|
await writeFile(
|
|
path.join(gitDirectory, "paseo-plugin.json"),
|
|
JSON.stringify({
|
|
id: "git-cli-e2e",
|
|
requirements: { paseo: ">=999.0.0" },
|
|
build: [
|
|
[
|
|
process.execPath,
|
|
"-e",
|
|
'require("node:fs").writeFileSync(process.argv[1], "ran")',
|
|
buildMarker,
|
|
],
|
|
],
|
|
}),
|
|
);
|
|
await git(gitDirectory, ["add", "-A"]);
|
|
await git(gitDirectory, ["commit", "-m", "requires a future Paseo"]);
|
|
const incompatibleUpdate = await context.paseo(["plugin", "update", "git-cli-e2e", "--json"]);
|
|
assert.equal(incompatibleUpdate.exitCode, 1);
|
|
assert.match(incompatibleUpdate.stderr, /requires Paseo >=999.0.0/);
|
|
await assert.rejects(readFile(buildMarker), { code: "ENOENT" });
|
|
const retained = await context.paseo(["plugin", "ls", "git-cli-e2e", "--json"]);
|
|
assert.equal(retained.exitCode, 0, retained.stderr);
|
|
assert.equal(JSON.parse(retained.stdout)[0].commit, installedCommit);
|
|
assert.equal(JSON.parse(retained.stdout)[0].status, "running");
|
|
const incompatibleAdd = await context.paseo([
|
|
"plugin",
|
|
"add",
|
|
pathToFileURL(gitDirectory).href,
|
|
"--id",
|
|
"future-plugin",
|
|
"--json",
|
|
]);
|
|
assert.equal(incompatibleAdd.exitCode, 1);
|
|
assert.match(incompatibleAdd.stderr, /requires Paseo >=999.0.0/);
|
|
await assert.rejects(readFile(buildMarker), { code: "ENOENT" });
|
|
|
|
const reload = await context.paseo(["plugin", "reload", "cli-e2e", "--json"]);
|
|
assert.equal(reload.exitCode, 0, reload.stderr);
|
|
assert.equal(JSON.parse(reload.stdout).status, "running");
|
|
|
|
const disable = await context.paseo(["plugin", "disable", "cli-e2e", "--json"]);
|
|
assert.equal(disable.exitCode, 0, disable.stderr);
|
|
assert.equal(JSON.parse(disable.stdout).status, "disabled");
|
|
|
|
const enable = await context.paseo(["plugin", "enable", "cli-e2e", "--json"]);
|
|
assert.equal(enable.exitCode, 0, enable.stderr);
|
|
assert.equal(JSON.parse(enable.stdout).status, "running");
|
|
|
|
const remove = await context.paseo(["plugin", "remove", "cli-e2e", "--json"]);
|
|
assert.equal(remove.exitCode, 0, remove.stderr);
|
|
const removeGit = await context.paseo(["plugin", "remove", "git-cli-e2e", "--json"]);
|
|
assert.equal(removeGit.exitCode, 0, removeGit.stderr);
|
|
const removeScaffold = await context.paseo(["plugin", "remove", "authored-plugin", "--json"]);
|
|
assert.equal(removeScaffold.exitCode, 0, removeScaffold.stderr);
|
|
const list = await context.paseo(["plugin", "ls", "--json"]);
|
|
assert.equal(list.exitCode, 0, list.stderr);
|
|
assert.deepEqual(JSON.parse(list.stdout), []);
|
|
} finally {
|
|
await context.stop();
|
|
await rm(directory, { recursive: true, force: true });
|
|
await rm(gitDirectory, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
await main();
|