mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 07:04:52 +09:00
* Rely on automatic discovery for installer plugins and document plugin package discovery requirements
127 lines
5.1 KiB
JavaScript
127 lines
5.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { promisify } from "node:util";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const installer = path.resolve(import.meta.dirname, "../packages/create/create.js");
|
|
const discoveryUrl = new URL("../packages/lib/dist/node/src/load_plugin_list.js", import.meta.url).href;
|
|
|
|
describe("create installer CLI", function() {
|
|
this.timeout(10000);
|
|
let directory;
|
|
|
|
beforeEach(async function() {
|
|
directory = await fs.mkdtemp(path.join(os.tmpdir(), "clusterio install #"));
|
|
await fs.writeFile(path.join(directory, "package.json"), JSON.stringify({
|
|
name: "clusterio-install", private: true,
|
|
}));
|
|
});
|
|
|
|
afterEach(async function() {
|
|
await fs.rm(directory, { recursive: true, force: true, maxRetries: 10 });
|
|
});
|
|
|
|
async function addPlugin(name, type, source) {
|
|
const pluginDir = path.join(directory, "node_modules", name);
|
|
await fs.mkdir(pluginDir, { recursive: true });
|
|
await fs.writeFile(path.join(pluginDir, "package.json"), JSON.stringify({
|
|
name, version: "1.0.0", type, main: "index.js", keywords: ["clusterio-plugin"],
|
|
}));
|
|
await fs.writeFile(path.join(pluginDir, "index.js"), source);
|
|
const packagePath = path.join(directory, "package.json");
|
|
const pkg = JSON.parse(await fs.readFile(packagePath));
|
|
pkg.dependencies = { ...pkg.dependencies, [name]: "1.0.0" };
|
|
await fs.writeFile(packagePath, JSON.stringify(pkg));
|
|
}
|
|
|
|
function runInstaller(plugins, installFails = false) {
|
|
// Only replace npm's subprocess: fixtures stand in for downloaded packages.
|
|
// Run the actual CLI from outside the installation, like npm init does.
|
|
const mockNpm = `
|
|
import assert from "node:assert/strict";
|
|
import childProcess from "node:child_process";
|
|
import { PassThrough } from "node:stream";
|
|
childProcess.execFile = (command, options, callback) => {
|
|
assert.match(command, /^npm(?:\\.cmd)? install /);
|
|
const stdout = new PassThrough();
|
|
const stderr = new PassThrough();
|
|
queueMicrotask(() => {
|
|
stdout.end();
|
|
stderr.end();
|
|
callback(${installFails} ? new Error("npm install failed") : null, "", "");
|
|
});
|
|
return { stdout, stderr };
|
|
};
|
|
`;
|
|
return execFileAsync(process.execPath, [
|
|
"--import", `data:text/javascript,${encodeURIComponent(mockNpm)}`,
|
|
installer, "--mode", "plugins",
|
|
...(process.platform === "linux" ? ["--allow-install-as-root"] : []),
|
|
"--plugins", ...plugins,
|
|
], { cwd: directory });
|
|
}
|
|
|
|
it("installs plugins without importing them or writing a plugin list", async function() {
|
|
await addPlugin("@installer-test/deferred", "module", 'throw new Error("installer must not import plugins");');
|
|
const { stdout } = await runInstaller(["@installer-test/deferred"]);
|
|
assert.match(stdout, /Successfully installed plugins/);
|
|
await assert.rejects(fs.access(path.join(directory, "plugin-list.json")), { code: "ENOENT" });
|
|
});
|
|
|
|
[false, true].forEach(existingList => {
|
|
it(`defers registration to discovery ${existingList ? "preserving existing entries" : "with no list"}`,
|
|
async function() {
|
|
await addPlugin("@installer-test/esm", "module", 'export const plugin = { name: "esm_plugin" };');
|
|
await addPlugin("@installer-test/commonjs", "commonjs", 'exports.plugin = { name: "cjs_plugin" };');
|
|
const listPath = path.join(directory, "plugin-list.json");
|
|
const previousEntries = existingList ? [["manual", "./plugins/manual"]] : [];
|
|
const previousContents = JSON.stringify(previousEntries);
|
|
if (existingList) {
|
|
await fs.writeFile(listPath, previousContents);
|
|
}
|
|
const { stdout } = await runInstaller(["@installer-test/esm", "@installer-test/commonjs"]);
|
|
assert.match(stdout, /Successfully installed plugins/);
|
|
if (existingList) {
|
|
assert.equal(await fs.readFile(listPath, "utf8"), previousContents);
|
|
} else {
|
|
await assert.rejects(fs.access(listPath), { code: "ENOENT" });
|
|
}
|
|
|
|
// Use the real discovery function invoked by controller/host/ctl commands.
|
|
await execFileAsync(process.execPath, [
|
|
"--input-type=module", "--eval",
|
|
`import { loadPluginList } from ${JSON.stringify(discoveryUrl)};
|
|
await loadPluginList("plugin-list.json");`,
|
|
], { cwd: directory });
|
|
assert.deepEqual(JSON.parse(await fs.readFile(listPath)), [
|
|
...previousEntries,
|
|
["esm_plugin", "@installer-test/esm"],
|
|
["cjs_plugin", "@installer-test/commonjs"],
|
|
]);
|
|
}
|
|
);
|
|
});
|
|
|
|
it("exits with failure when npm installation fails", async function() {
|
|
await assert.rejects(runInstaller(["@installer-test/missing"], true), err => {
|
|
assert.equal(err.code, 1);
|
|
assert.match(err.stdout + err.stderr, /Failed to install: npm install failed/);
|
|
assert.doesNotMatch(err.stdout, /Successfully installed/);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
it("exits with failure on an unexpected installer error", async function() {
|
|
await fs.writeFile(path.join(directory, "package.json"), "null");
|
|
await assert.rejects(runInstaller([]), err => {
|
|
assert.equal(err.code, 1);
|
|
assert.match(err.stdout + err.stderr, /TypeError/);
|
|
assert.doesNotMatch(err.stdout, /Successfully installed/);
|
|
return true;
|
|
});
|
|
});
|
|
});
|