semantic-release/test/get-config.test.js
2022-11-23 16:02:51 -08:00

599 lines
24 KiB
JavaScript

import path from "node:path";
import { format } from "node:util";
import test from "ava";
import fsExtra from "fs-extra";
import { omit } from "lodash-es";
import * as td from "testdouble";
import yaml from "js-yaml";
import { gitAddConfig, gitCommits, gitRepo, gitShallowClone, gitTagVersion } from "./helpers/git-utils.js";
const { outputJson, writeFile } = fsExtra;
const pluginsConfig = { foo: "bar", baz: "qux" };
let plugins;
const DEFAULT_PLUGINS = [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
"@semantic-release/github",
];
test.beforeEach(async (t) => {
plugins = (await td.replaceEsm("../lib/plugins/index.js")).default;
t.context.getConfig = (await import("../lib/get-config.js")).default;
});
test.afterEach.always((t) => {
td.reset();
});
test("Default values, reading repositoryUrl from package.json", async (t) => {
const pkg = { repository: "https://host.null/owner/package.git" };
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo(true);
await gitCommits(["First"], { cwd });
await gitTagVersion("v1.0.0", undefined, { cwd });
await gitTagVersion("v1.1.0", undefined, { cwd });
// Add remote.origin.url config
await gitAddConfig("remote.origin.url", "git@host.null:owner/repo.git", { cwd });
// Create package.json in repository root
await outputJson(path.resolve(cwd, "package.json"), pkg);
const { options: result } = await t.context.getConfig({ cwd });
// Verify the default options are set
t.deepEqual(result.branches, [
"+([0-9])?(.{+([0-9]),x}).x",
"master",
"next",
"next-major",
{ name: "beta", prerelease: true },
{ name: "alpha", prerelease: true },
]);
t.is(result.repositoryUrl, "https://host.null/owner/package.git");
t.is(result.tagFormat, `v\${version}`);
});
test("Default values, reading repositoryUrl from repo if not set in package.json", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo(true);
// Add remote.origin.url config
await gitAddConfig("remote.origin.url", "https://host.null/owner/module.git", { cwd });
const { options: result } = await t.context.getConfig({ cwd });
// Verify the default options are set
t.deepEqual(result.branches, [
"+([0-9])?(.{+([0-9]),x}).x",
"master",
"next",
"next-major",
{ name: "beta", prerelease: true },
{ name: "alpha", prerelease: true },
]);
t.is(result.repositoryUrl, "https://host.null/owner/module.git");
t.is(result.tagFormat, `v\${version}`);
});
test("Default values, reading repositoryUrl (http url) from package.json if not set in repo", async (t) => {
const pkg = { repository: "https://host.null/owner/module.git" };
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
// Create package.json in repository root
await outputJson(path.resolve(cwd, "package.json"), pkg);
const { options: result } = await t.context.getConfig({ cwd });
// Verify the default options are set
t.deepEqual(result.branches, [
"+([0-9])?(.{+([0-9]),x}).x",
"master",
"next",
"next-major",
{ name: "beta", prerelease: true },
{ name: "alpha", prerelease: true },
]);
t.is(result.repositoryUrl, "https://host.null/owner/module.git");
t.is(result.tagFormat, `v\${version}`);
});
test('Convert "ci" option to "noCi"', async (t) => {
const pkg = { repository: "https://host.null/owner/module.git", release: { ci: false } };
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
// Create package.json in repository root
await outputJson(path.resolve(cwd, "package.json"), pkg);
const { options: result } = await t.context.getConfig({ cwd });
t.is(result.noCi, true);
});
test.serial("Read options from package.json", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
generateNotes: "generateNotes",
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Verify the plugins module is called with the plugin options from package.json
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
// Create package.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: options });
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from package.json
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial("Read options from .releaserc.yml", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json in repository root
await writeFile(path.resolve(cwd, ".releaserc.yml"), yaml.dump(options));
// Verify the plugins module is called with the plugin options from package.json
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from package.json
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial("Read options from .releaserc.json", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json in repository root
await outputJson(path.resolve(cwd, ".releaserc.json"), options);
// Verify the plugins module is called with the plugin options from package.json
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from package.json
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial("Read options from .releaserc.js", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json in repository root
await writeFile(path.resolve(cwd, ".releaserc.js"), `module.exports = ${JSON.stringify(options)}`);
// Verify the plugins module is called with the plugin options from package.json
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from package.json
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial("Read options from .releaserc.cjs", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Create .releaserc.cjs in repository root
await writeFile(path.resolve(cwd, ".releaserc.cjs"), `module.exports = ${JSON.stringify(options)}`);
// Verify the plugins module is called with the plugin options from .releaserc.cjs
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from .releaserc.cjs
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial("Read options from release.config.js", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json in repository root
await writeFile(path.resolve(cwd, "release.config.js"), `module.exports = ${JSON.stringify(options)}`);
// Verify the plugins module is called with the plugin options from package.json
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from package.json
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial("Read options from release.config.cjs", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Verify the plugins module is called with the plugin options from release.config.cjs
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
// Create release.config.cjs in repository root
await writeFile(path.resolve(cwd, "release.config.cjs"), `module.exports = ${JSON.stringify(options)}`);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from release.config.cjs
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial("Prioritise CLI/API parameters over file configuration and git repo", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let { cwd, repositoryUrl } = await gitRepo();
await gitCommits(["First"], { cwd });
// Create a clone
cwd = await gitShallowClone(repositoryUrl);
const pkgOptions = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_pkg" },
branches: ["branch_pkg"],
};
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_cli" },
branches: ["branch_cli"],
repositoryUrl: "http://cli-url.com/owner/package",
tagFormat: `cli\${version}`,
plugins: false,
};
// Verify the plugins module is called with the plugin options from CLI/API
td.when(plugins({ cwd, options }, {})).thenResolve(pluginsConfig);
const pkg = { release: pkgOptions, repository: "git@host.null:owner/module.git" };
// Create package.json in repository root
await outputJson(path.resolve(cwd, "package.json"), pkg);
const result = await t.context.getConfig({ cwd }, options);
// Verify the options contains the plugin config from CLI/API
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial('Read configuration from file path in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = { extends: "./shareable.json" };
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
generateNotes: "generateNotes",
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: ["plugin-1", ["plugin-2", { plugin2Opt: "value" }]],
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "shareable.json"), options);
// Verify the plugins module is called with the plugin options from shareable.json
td.when(
plugins(
{ cwd, options },
{
analyzeCommits: "./shareable.json",
generateNotes: "./shareable.json",
"plugin-1": "./shareable.json",
"plugin-2": "./shareable.json",
}
)
).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from shareable.json
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial('Read configuration from module path in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = { extends: "shareable" };
const options = {
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
generateNotes: "generateNotes",
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "node_modules/shareable/index.json"), options);
// Verify the plugins module is called with the plugin options from shareable.json
td.when(plugins({ cwd, options }, { analyzeCommits: "shareable", generateNotes: "shareable" })).thenResolve(
pluginsConfig
);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from shareable.json
t.deepEqual(result, { options, plugins: pluginsConfig });
});
test.serial('Read configuration from an array of paths in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = { extends: ["./shareable1.json", "./shareable2.json"] };
const options1 = {
verifyRelease: "verifyRelease1",
analyzeCommits: { path: "analyzeCommits1", param: "analyzeCommits_param1" },
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
};
const options2 = {
verifyRelease: "verifyRelease2",
generateNotes: "generateNotes2",
analyzeCommits: { path: "analyzeCommits2", param: "analyzeCommits_param2" },
branches: ["test_branch"],
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "shareable1.json"), options1);
await outputJson(path.resolve(cwd, "shareable2.json"), options2);
const expectedOptions = { ...options1, ...options2, branches: ["test_branch"] };
// Verify the plugins module is called with the plugin options from shareable1.json and shareable2.json
td.when(
plugins(
{ options: expectedOptions, cwd },
{
verifyRelease1: "./shareable1.json",
verifyRelease2: "./shareable2.json",
generateNotes2: "./shareable2.json",
analyzeCommits1: "./shareable1.json",
analyzeCommits2: "./shareable2.json",
}
)
).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from shareable1.json and shareable2.json
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
});
test.serial('Prioritize configuration from config file over "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = {
extends: "./shareable.json",
branches: ["test_pkg"],
generateNotes: "generateNotes",
publish: [{ path: "publishPkg", param: "publishPkg_param" }],
};
const options1 = {
analyzeCommits: "analyzeCommits",
generateNotes: "generateNotesShareable",
publish: [{ path: "publishShareable", param: "publishShareable_param" }],
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "shareable.json"), options1);
const expectedOptions = omit({ ...options1, ...pkgOptions, branches: ["test_pkg"] }, "extends");
// Verify the plugins module is called with the plugin options from package.json and shareable.json
td.when(
plugins(
{ cwd, options: expectedOptions },
{
analyzeCommits: "./shareable.json",
generateNotesShareable: "./shareable.json",
publishShareable: "./shareable.json",
}
)
).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from package.json and shareable.json
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
});
test.serial('Prioritize configuration from cli/API options over "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const cliOptions = {
extends: "./shareable2.json",
branches: ["branch_opts"],
publish: [{ path: "publishOpts", param: "publishOpts_param" }],
repositoryUrl: "https://host.null/owner/module.git",
};
const pkgOptions = {
extends: "./shareable1.json",
branches: ["branch_pkg"],
generateNotes: "generateNotes",
publish: [{ path: "publishPkg", param: "publishPkg_param" }],
};
const options1 = {
analyzeCommits: "analyzeCommits1",
generateNotes: "generateNotesShareable1",
publish: [{ path: "publishShareable", param: "publishShareable_param1" }],
branches: ["test_branch1"],
repositoryUrl: "https://host.null/owner/module.git",
};
const options2 = {
analyzeCommits: "analyzeCommits2",
publish: [{ path: "publishShareable", param: "publishShareable_param2" }],
branches: ["test_branch2"],
tagFormat: `v\${version}`,
plugins: false,
};
// Create package.json, shareable1.json and shareable2.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "shareable1.json"), options1);
await outputJson(path.resolve(cwd, "shareable2.json"), options2);
const expectedOptions = omit({ ...options2, ...pkgOptions, ...cliOptions, branches: ["branch_opts"] }, "extends");
// Verify the plugins module is called with the plugin options from package.json and shareable2.json
td.when(
plugins(
{ cwd, options: expectedOptions },
{ analyzeCommits2: "./shareable2.json", publishShareable: "./shareable2.json" }
)
).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd }, cliOptions);
// Verify the options contains the plugin config from package.json and shareable2.json
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
});
test.serial('Allow to unset properties defined in shareable config with "null"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = {
extends: "./shareable.json",
analyzeCommits: null,
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
plugins: null,
};
const options1 = {
generateNotes: "generateNotes",
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
tagFormat: `v\${version}`,
plugins: ["test-plugin"],
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "shareable.json"), options1);
// Verify the plugins module is called with the plugin options from shareable.json and the default `plugins`
td.when(
plugins(
{
options: {
...omit(options1, "analyzeCommits"),
...omit(pkgOptions, ["extends", "analyzeCommits"]),
plugins: DEFAULT_PLUGINS,
},
cwd,
},
{
generateNotes: "./shareable.json",
analyzeCommits: "./shareable.json",
"test-plugin": "./shareable.json",
}
)
).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from shareable.json and the default `plugins`
t.deepEqual(result, {
options: {
...omit(options1, ["analyzeCommits"]),
...omit(pkgOptions, ["extends", "analyzeCommits"]),
plugins: DEFAULT_PLUGINS,
},
plugins: pluginsConfig,
});
});
test.serial('Allow to unset properties defined in shareable config with "undefined"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = {
extends: "./shareable.json",
analyzeCommits: undefined,
branches: ["test_branch"],
repositoryUrl: "https://host.null/owner/module.git",
};
const options1 = {
generateNotes: "generateNotes",
analyzeCommits: { path: "analyzeCommits", param: "analyzeCommits_param" },
tagFormat: `v\${version}`,
plugins: false,
};
// Create release.config.js and shareable.json in repository root
await writeFile(path.resolve(cwd, "release.config.js"), `module.exports = ${format(pkgOptions)}`);
await outputJson(path.resolve(cwd, "shareable.json"), options1);
const expectedOptions = {
...omit(options1, "analyzeCommits"),
...omit(pkgOptions, ["extends", "analyzeCommits"]),
branches: ["test_branch"],
};
// Verify the plugins module is called with the plugin options from shareable.json
td.when(
plugins(
{ options: expectedOptions, cwd },
{ generateNotes: "./shareable.json", analyzeCommits: "./shareable.json" }
)
).thenResolve(pluginsConfig);
const result = await t.context.getConfig({ cwd });
// Verify the options contains the plugin config from shareable.json
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
});
test("Throw an Error if one of the shareable config cannot be found", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = { extends: ["./shareable1.json", "non-existing-path"] };
const options1 = { analyzeCommits: "analyzeCommits" };
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "shareable1.json"), options1);
await t.throwsAsync(t.context.getConfig({ cwd }), {
message: /Cannot find module 'non-existing-path'/,
code: "MODULE_NOT_FOUND",
});
});
test('Convert "ci" option to "noCi" when set from extended config', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd } = await gitRepo();
const pkgOptions = { extends: "./no-ci.json" };
const options = {
ci: false,
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
await outputJson(path.resolve(cwd, "no-ci.json"), options);
const { options: result } = await t.context.getConfig({ cwd });
t.is(result.ci, false);
t.is(result.noCi, true);
});