import test from "ava"; import { escapeRegExp } from "lodash-es"; import * as td from "testdouble"; import { stub } from "sinon"; import { SECRET_REPLACEMENT } from "../lib/definitions/constants.js"; let previousArgv; let previousEnv; test.beforeEach((t) => { t.context.logs = ""; t.context.errors = ""; t.context.stdout = stub(process.stdout, "write").callsFake((value) => { t.context.logs += value.toString(); }); t.context.stderr = stub(process.stderr, "write").callsFake((value) => { t.context.errors += value.toString(); }); previousArgv = process.argv; previousEnv = process.env; }); test.afterEach.always((t) => { t.context.stdout.restore(); t.context.stderr.restore(); process.argv = previousArgv; process.env = previousEnv; td.reset(); }); test.serial("Pass options to semantic-release API", async (t) => { const argv = [ "", "", "-b", "master", "next", "-r", "https://github/com/owner/repo.git", "-t", `v\${version}`, "-p", "plugin1", "plugin2", "-e", "config1", "config2", "--verify-conditions", "condition1", "condition2", "--analyze-commits", "analyze", "--verify-release", "verify1", "verify2", "--generate-notes", "notes", "--prepare", "prepare1", "prepare2", "--publish", "publish1", "publish2", "--success", "success1", "success2", "--fail", "fail1", "fail2", "--debug", "-d", ]; const index = await td.replaceEsm("../index.js"); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); td.verify( index.default({ branches: ["master", "next"], b: ["master", "next"], "repository-url": "https://github/com/owner/repo.git", repositoryUrl: "https://github/com/owner/repo.git", r: "https://github/com/owner/repo.git", "tag-format": `v\${version}`, tagFormat: `v\${version}`, t: `v\${version}`, plugins: ["plugin1", "plugin2"], p: ["plugin1", "plugin2"], extends: ["config1", "config2"], e: ["config1", "config2"], "dry-run": true, dryRun: true, d: true, verifyConditions: ["condition1", "condition2"], "verify-conditions": ["condition1", "condition2"], analyzeCommits: "analyze", "analyze-commits": "analyze", verifyRelease: ["verify1", "verify2"], "verify-release": ["verify1", "verify2"], generateNotes: ["notes"], "generate-notes": ["notes"], prepare: ["prepare1", "prepare2"], publish: ["publish1", "publish2"], success: ["success1", "success2"], fail: ["fail1", "fail2"], debug: true, _: [], $0: "", }) ); t.is(exitCode, 0); }); test.serial("Pass options to semantic-release API with alias arguments", async (t) => { const argv = [ "", "", "--branches", "master", "--repository-url", "https://github/com/owner/repo.git", "--tag-format", `v\${version}`, "--plugins", "plugin1", "plugin2", "--extends", "config1", "config2", "--dry-run", ]; const index = await td.replaceEsm("../index.js"); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); td.verify( index.default({ branches: ["master"], b: ["master"], "repository-url": "https://github/com/owner/repo.git", repositoryUrl: "https://github/com/owner/repo.git", r: "https://github/com/owner/repo.git", "tag-format": `v\${version}`, tagFormat: `v\${version}`, t: `v\${version}`, plugins: ["plugin1", "plugin2"], p: ["plugin1", "plugin2"], extends: ["config1", "config2"], e: ["config1", "config2"], "dry-run": true, dryRun: true, d: true, _: [], $0: "", }) ); t.is(exitCode, 0); }); test.serial("Pass unknown options to semantic-release API", async (t) => { const argv = ["", "", "--bool", "--first-option", "value1", "--second-option", "value2", "--second-option", "value3"]; const index = await td.replaceEsm("../index.js"); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); td.verify( index.default({ bool: true, firstOption: "value1", "first-option": "value1", secondOption: ["value2", "value3"], "second-option": ["value2", "value3"], _: [], $0: "", }) ); t.is(exitCode, 0); }); test.serial('Pass empty Array to semantic-release API for list option set to "false"', async (t) => { const argv = ["", "", "--publish", "false"]; const index = await td.replaceEsm("../index.js"); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); td.verify(index.default({ publish: [], _: [], $0: "" })); t.is(exitCode, 0); }); test.serial("Do not set properties in option for which arg is not in command line", async (t) => { const run = stub().resolves(true); const argv = ["", "", "-b", "master"]; await td.replaceEsm("../index.js", null, run); process.argv = argv; const cli = (await import("../cli.js")).default; await cli(); t.false("ci" in run.args[0][0]); t.false("d" in run.args[0][0]); t.false("dry-run" in run.args[0][0]); t.false("debug" in run.args[0][0]); t.false("r" in run.args[0][0]); t.false("t" in run.args[0][0]); t.false("p" in run.args[0][0]); t.false("e" in run.args[0][0]); }); test.serial("Display help", async (t) => { const run = stub().resolves(true); const argv = ["", "", "--help"]; await td.replaceEsm("../index.js", null, run); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); t.regex(t.context.logs, /Run automated package publishing/); t.is(exitCode, 0); }); test.serial("Return error exitCode and prints help if called with a command", async (t) => { const run = stub().resolves(true); const argv = ["", "", "pre"]; await td.replaceEsm("../index.js", null, run); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); t.regex(t.context.errors, /Run automated package publishing/); t.regex(t.context.errors, /Too many non-option arguments/); t.is(exitCode, 1); }); test.serial("Return error exitCode if multiple plugin are set for single plugin", async (t) => { const run = stub().resolves(true); const argv = ["", "", "--analyze-commits", "analyze1", "analyze2"]; await td.replaceEsm("../index.js", null, run); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); t.regex(t.context.errors, /Run automated package publishing/); t.regex(t.context.errors, /Too many non-option arguments/); t.is(exitCode, 1); }); test.serial("Return error exitCode if semantic-release throw error", async (t) => { const argv = ["", ""]; const index = await td.replaceEsm("../index.js"); td.when(index.default({ _: [], $0: "" })).thenReject(new Error("semantic-release error")); process.argv = argv; const cli = (await import("../cli.js")).default; const exitCode = await cli(); t.regex(t.context.errors, /semantic-release error/); t.is(exitCode, 1); }); test.serial("Hide sensitive environment variable values from the logs", async (t) => { const env = { MY_TOKEN: "secret token" }; const argv = ["", ""]; const index = await td.replaceEsm("../index.js"); td.when(index.default({ _: [], $0: "" })).thenReject(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`)); process.argv = argv; process.env = { ...process.env, ...env }; const cli = (await import("../cli.js")).default; const exitCode = await cli(); t.regex(t.context.errors, new RegExp(`Throw error: Exposing token ${escapeRegExp(SECRET_REPLACEMENT)}`)); t.is(exitCode, 1); });