#!/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 { await execFileAsync("git", args, { cwd }); } async function main(): Promise { 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();