fix(deps): bump @semantic-release/npm to ^10.0.0-beta.1 (#2670)

This commit is contained in:
Gregor Martynus
2023-01-14 14:14:04 -08:00
committed by GitHub
parent e607e234f3
commit 6a83cb53e5
39 changed files with 1686 additions and 1453 deletions
+154 -154
View File
@@ -1,277 +1,277 @@
import test from 'ava';
import {union} from 'lodash-es';
import semver from 'semver';
import * as td from 'testdouble';
import test from "ava";
import { union } from "lodash-es";
import semver from "semver";
import * as td from "testdouble";
const getBranch = (branches, branch) => branches.find(({name}) => name === branch);
const release = (branches, name, version) => getBranch(branches, name).tags.push({version});
const getBranch = (branches, branch) => branches.find(({ name }) => name === branch);
const release = (branches, name, version) => getBranch(branches, name).tags.push({ version });
const merge = (branches, source, target, tag) => {
getBranch(branches, target).tags = union(
getBranch(branches, source).tags.filter(({version}) => !tag || semver.cmp(version, '<=', tag)),
getBranch(branches, source).tags.filter(({ version }) => !tag || semver.cmp(version, "<=", tag)),
getBranch(branches, target).tags
);
};
const remoteBranches = [];
const repositoryUrl = 'repositoryUrl';
const repositoryUrl = "repositoryUrl";
let expand, getTags, getBranches;
test.beforeEach(async (t) => {
getTags = (await td.replaceEsm('../../lib/branches/get-tags.js')).default;
expand = (await td.replaceEsm('../../lib/branches/expand.js')).default;
getBranches = (await import('../../lib/branches/index.js')).default;
})
getTags = (await td.replaceEsm("../../lib/branches/get-tags.js")).default;
expand = (await td.replaceEsm("../../lib/branches/expand.js")).default;
getBranches = (await import("../../lib/branches/index.js")).default;
});
test.afterEach.always((t) => {
td.reset();
});
test.serial('Enforce ranges with branching release workflow', async (t) => {
test.serial("Enforce ranges with branching release workflow", async (t) => {
const branches = [
{name: '1.x', tags: []},
{name: '1.0.x', tags: []},
{name: 'master', tags: []},
{name: 'next', tags: []},
{name: 'next-major', tags: []},
{name: 'beta', prerelease: true, tags: []},
{name: 'alpha', prerelease: true, tags: []},
{ name: "1.x", tags: [] },
{ name: "1.0.x", tags: [] },
{ name: "master", tags: [] },
{ name: "next", tags: [] },
{ name: "next-major", tags: [] },
{ name: "beta", prerelease: true, tags: [] },
{ name: "alpha", prerelease: true, tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
let result = (await getBranches(repositoryUrl, 'master', context)).map(({name, range}) => ({name, range,}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
t.is(getBranch(result, 'master').range, '>=1.0.0');
t.is(getBranch(result, 'next').range, '>=1.0.0');
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
let result = (await getBranches(repositoryUrl, "master", context)).map(({ name, range }) => ({ name, range }));
t.is(getBranch(result, "1.0.x").range, ">=1.0.0 <1.0.0", "Cannot release on 1.0.x before a releasing on master");
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.0", "Cannot release on 1.x before a releasing on master");
t.is(getBranch(result, "master").range, ">=1.0.0");
t.is(getBranch(result, "next").range, ">=1.0.0");
t.is(getBranch(result, "next-major").range, ">=1.0.0");
release(branches, 'master', '1.0.0');
result = (await getBranches('repositoryUrl', 'master', context)).map(({name, range}) => ({name, range}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
t.is(getBranch(result, 'master').range, '>=1.0.0');
t.is(getBranch(result, 'next').range, '>=1.0.0');
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
release(branches, "master", "1.0.0");
result = (await getBranches("repositoryUrl", "master", context)).map(({ name, range }) => ({ name, range }));
t.is(getBranch(result, "1.0.x").range, ">=1.0.0 <1.0.0", "Cannot release on 1.0.x before a releasing on master");
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.0", "Cannot release on 1.x before a releasing on master");
t.is(getBranch(result, "master").range, ">=1.0.0");
t.is(getBranch(result, "next").range, ">=1.0.0");
t.is(getBranch(result, "next-major").range, ">=1.0.0");
release(branches, 'master', '1.0.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "master", "1.0.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1", "Can release only > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.0.1", "Can release only > than 1.0.1 on next");
t.is(getBranch(result, "next-major").range, ">=1.0.1", "Can release only > than 1.0.1 on next-major");
merge(branches, 'master', 'next');
merge(branches, 'master', 'next-major');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "next");
merge(branches, "master", "next-major");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1", "Can release only > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.0.1", "Can release only > than 1.0.1 on next");
t.is(getBranch(result, "next-major").range, ">=1.0.1", "Can release only > than 1.0.1 on next-major");
release(branches, 'next', '1.1.0');
release(branches, 'next', '1.1.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "next", "1.1.0");
release(branches, "next", "1.1.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1', 'Can release only > than 1.1.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.1.1', 'Can release > than 1.1.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1 <1.1.0", "Can release only patch, > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.1.1", "Can release only > than 1.1.1 on next");
t.is(getBranch(result, "next-major").range, ">=1.1.1", "Can release > than 1.1.1 on next-major");
release(branches, 'next-major', '2.0.0');
release(branches, 'next-major', '2.0.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "next-major", "2.0.0");
release(branches, "next-major", "2.0.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.0 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1 <1.1.0", "Can release only patch, > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.1.1 <2.0.0", "Can release only patch or minor, > than 1.1.0 on next");
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release any version, > than 2.0.1 on next-major");
merge(branches, 'next-major', 'beta');
release(branches, 'beta', '3.0.0-beta.1');
merge(branches, 'beta', 'alpha');
release(branches, 'alpha', '4.0.0-alpha.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "next-major", "beta");
release(branches, "beta", "3.0.0-beta.1");
merge(branches, "beta", "alpha");
release(branches, "alpha", "4.0.0-alpha.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release any version, > than 2.0.1 on next-major");
merge(branches, 'master', '1.0.x');
merge(branches, 'master', '1.x');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "1.0.x");
merge(branches, "master", "1.x");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, "master").range, ">=1.0.1 <1.1.0", "Can release only patch, > than 1.0.1 on master");
t.is(
getBranch(result, '1.0.x').range,
'>=1.0.1 <1.0.1',
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
getBranch(result, "1.0.x").range,
">=1.0.1 <1.0.1",
"Cannot release on 1.0.x before >= 1.1.0 is released on master"
);
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.1', 'Cannot release on 1.x before >= 1.2.0 is released on master');
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.1", "Cannot release on 1.x before >= 1.2.0 is released on master");
release(branches, 'master', '1.0.2');
release(branches, 'master', '1.0.3');
release(branches, 'master', '1.0.4');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "master", "1.0.2");
release(branches, "master", "1.0.3");
release(branches, "master", "1.0.4");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.4 <1.1.0', 'Can release only patch, > than 1.0.4 on master');
t.is(getBranch(result, "master").range, ">=1.0.4 <1.1.0", "Can release only patch, > than 1.0.4 on master");
t.is(
getBranch(result, '1.0.x').range,
'>=1.0.1 <1.0.2',
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
getBranch(result, "1.0.x").range,
">=1.0.1 <1.0.2",
"Cannot release on 1.0.x before >= 1.1.0 is released on master"
);
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 1.2.0 is released on master');
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.2", "Cannot release on 1.x before >= 1.2.0 is released on master");
merge(branches, 'next', 'master');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "next", "master");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.1 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
t.is(getBranch(result, "next").range, ">=1.1.1 <2.0.0", "Can release only patch or minor, > than 1.1.1 on next");
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release any version, > than 2.0.1 on next-major");
t.is(
getBranch(result, '1.0.x').range,
'>=1.0.1 <1.0.2',
'Cannot release on 1.0.x before 1.0.x version from master are merged'
getBranch(result, "1.0.x").range,
">=1.0.1 <1.0.2",
"Cannot release on 1.0.x before 1.0.x version from master are merged"
);
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 2.0.0 is released on master');
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.2", "Cannot release on 1.x before >= 2.0.0 is released on master");
merge(branches, 'master', '1.0.x', '1.0.4');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "1.0.x", "1.0.4");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.1.0', 'Cannot release on 1.x before >= 2.0.0 is released on master');
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
t.is(getBranch(result, "1.0.x").range, ">=1.0.4 <1.1.0", "Can release on 1.0.x only within range");
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.1.0", "Cannot release on 1.x before >= 2.0.0 is released on master");
merge(branches, 'master', '1.x');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "1.x");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.1.1 <1.1.1', 'Cannot release on 1.x before >= 2.0.0 is released on master');
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
t.is(getBranch(result, "1.0.x").range, ">=1.0.4 <1.1.0", "Can release on 1.0.x only within range");
t.is(getBranch(result, "1.x").range, ">=1.1.1 <1.1.1", "Cannot release on 1.x before >= 2.0.0 is released on master");
merge(branches, 'next-major', 'next');
merge(branches, 'next', 'master');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "next-major", "next");
merge(branches, "next", "master");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=2.0.1', 'Can release only > than 2.0.1 on master');
t.is(getBranch(result, 'next').range, '>=2.0.1', 'Can release only > than 2.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release only > than 2.0.1 on next-major');
t.is(getBranch(result, '1.x').range, '>=1.1.1 <2.0.0', 'Can release on 1.x only within range');
t.is(getBranch(result, "master").range, ">=2.0.1", "Can release only > than 2.0.1 on master");
t.is(getBranch(result, "next").range, ">=2.0.1", "Can release only > than 2.0.1 on next");
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release only > than 2.0.1 on next-major");
t.is(getBranch(result, "1.x").range, ">=1.1.1 <2.0.0", "Can release on 1.x only within range");
merge(branches, 'beta', 'master');
release(branches, 'master', '3.0.0');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "beta", "master");
release(branches, "master", "3.0.0");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=3.0.0', 'Can release only > than 3.0.0 on master');
t.is(getBranch(result, 'next').range, '>=3.0.0', 'Can release only > than 3.0.0 on next');
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only > than 3.0.0 on next-major');
t.is(getBranch(result, "master").range, ">=3.0.0", "Can release only > than 3.0.0 on master");
t.is(getBranch(result, "next").range, ">=3.0.0", "Can release only > than 3.0.0 on next");
t.is(getBranch(result, "next-major").range, ">=3.0.0", "Can release only > than 3.0.0 on next-major");
branches.push({name: '1.1.x', tags: []});
merge(branches, '1.x', '1.1.x');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
branches.push({ name: "1.1.x", tags: [] });
merge(branches, "1.x", "1.1.x");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.1.x').range, '>=1.1.1 <1.2.0', 'Can release on 1.1.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
t.is(getBranch(result, "1.0.x").range, ">=1.0.4 <1.1.0", "Can release on 1.0.x only within range");
t.is(getBranch(result, "1.1.x").range, ">=1.1.1 <1.2.0", "Can release on 1.1.x only within range");
t.is(getBranch(result, "1.x").range, ">=1.2.0 <2.0.0", "Can release on 1.x only within range");
});
test.serial('Throw SemanticReleaseError for invalid configurations', async (t) => {
test.serial("Throw SemanticReleaseError for invalid configurations", async (t) => {
const branches = [
{name: '123', range: '123', tags: []},
{name: '1.x', tags: []},
{name: 'maintenance-1', range: '1.x', tags: []},
{name: '1.x.x', tags: []},
{name: 'beta', prerelease: '', tags: []},
{name: 'alpha', prerelease: 'alpha', tags: []},
{name: 'preview', prerelease: 'alpha', tags: []},
{ name: "123", range: "123", tags: [] },
{ name: "1.x", tags: [] },
{ name: "maintenance-1", range: "1.x", tags: [] },
{ name: "1.x.x", tags: [] },
{ name: "beta", prerelease: "", tags: [] },
{ name: "alpha", prerelease: "alpha", tags: [] },
{ name: "preview", prerelease: "alpha", tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
const error = await t.throwsAsync(getBranches(repositoryUrl, 'master', context));
const error = await t.throwsAsync(getBranches(repositoryUrl, "master", context));
const errors = [...error.errors];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EMAINTENANCEBRANCH');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EMAINTENANCEBRANCH");
t.truthy(errors[0].message);
t.truthy(errors[0].details);
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EMAINTENANCEBRANCHES');
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EMAINTENANCEBRANCHES");
t.truthy(errors[1].message);
t.truthy(errors[1].details);
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EPRERELEASEBRANCH');
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPRERELEASEBRANCH");
t.truthy(errors[2].message);
t.truthy(errors[2].details);
t.is(errors[3].name, 'SemanticReleaseError');
t.is(errors[3].code, 'EPRERELEASEBRANCHES');
t.is(errors[3].name, "SemanticReleaseError");
t.is(errors[3].code, "EPRERELEASEBRANCHES");
t.truthy(errors[3].message);
t.truthy(errors[3].details);
t.is(errors[4].name, 'SemanticReleaseError');
t.is(errors[4].code, 'ERELEASEBRANCHES');
t.is(errors[4].name, "SemanticReleaseError");
t.is(errors[4].code, "ERELEASEBRANCHES");
t.truthy(errors[4].message);
t.truthy(errors[4].details);
});
test.serial('Throw a SemanticReleaseError if there is duplicate branches', async (t) => {
test.serial("Throw a SemanticReleaseError if there is duplicate branches", async (t) => {
const branches = [
{name: 'master', tags: []},
{name: 'master', tags: []},
{ name: "master", tags: [] },
{ name: "master", tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, 'master', context))).errors];
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, "master", context))).errors];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EDUPLICATEBRANCHES');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EDUPLICATEBRANCHES");
t.truthy(errors[0].message);
t.truthy(errors[0].details);
});
test.serial('Throw a SemanticReleaseError for each invalid branch name', async (t) => {
test.serial("Throw a SemanticReleaseError for each invalid branch name", async (t) => {
const branches = [
{name: '~master', tags: []},
{name: '^master', tags: []},
{ name: "~master", tags: [] },
{ name: "^master", tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
const remoteBranches = [];
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, 'master', context))).errors];
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, "master", context))).errors];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EINVALIDBRANCHNAME');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EINVALIDBRANCHNAME");
t.truthy(errors[0].message);
t.truthy(errors[0].details);
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EINVALIDBRANCHNAME');
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EINVALIDBRANCHNAME");
t.truthy(errors[1].message);
t.truthy(errors[1].details);
});
+44 -44
View File
@@ -1,54 +1,54 @@
import test from 'ava';
import expand from '../../lib/branches/expand.js';
import {gitCheckout, gitCommits, gitPush, gitRepo} from '../helpers/git-utils.js';
import test from "ava";
import expand from "../../lib/branches/expand.js";
import { gitCheckout, gitCommits, gitPush, gitRepo } from "../helpers/git-utils.js";
test('Expand branches defined with globs', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('1.0.x', true, {cwd});
await gitCommits(['Second'], {cwd});
await gitPush(repositoryUrl, '1.0.x', {cwd});
await gitCheckout('1.x.x', true, {cwd});
await gitCommits(['Third'], {cwd});
await gitPush(repositoryUrl, '1.x.x', {cwd});
await gitCheckout('2.x', true, {cwd});
await gitCommits(['Fourth'], {cwd});
await gitPush(repositoryUrl, '2.x', {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Fifth'], {cwd});
await gitPush(repositoryUrl, 'next', {cwd});
await gitCheckout('pre/foo', true, {cwd});
await gitCommits(['Sixth'], {cwd});
await gitPush(repositoryUrl, 'pre/foo', {cwd});
await gitCheckout('pre/bar', true, {cwd});
await gitCommits(['Seventh'], {cwd});
await gitPush(repositoryUrl, 'pre/bar', {cwd});
await gitCheckout('beta', true, {cwd});
await gitCommits(['Eighth'], {cwd});
await gitPush(repositoryUrl, 'beta', {cwd});
test("Expand branches defined with globs", async (t) => {
const { cwd, repositoryUrl } = await gitRepo(true);
await gitCommits(["First"], { cwd });
await gitPush(repositoryUrl, "master", { cwd });
await gitCheckout("1.0.x", true, { cwd });
await gitCommits(["Second"], { cwd });
await gitPush(repositoryUrl, "1.0.x", { cwd });
await gitCheckout("1.x.x", true, { cwd });
await gitCommits(["Third"], { cwd });
await gitPush(repositoryUrl, "1.x.x", { cwd });
await gitCheckout("2.x", true, { cwd });
await gitCommits(["Fourth"], { cwd });
await gitPush(repositoryUrl, "2.x", { cwd });
await gitCheckout("next", true, { cwd });
await gitCommits(["Fifth"], { cwd });
await gitPush(repositoryUrl, "next", { cwd });
await gitCheckout("pre/foo", true, { cwd });
await gitCommits(["Sixth"], { cwd });
await gitPush(repositoryUrl, "pre/foo", { cwd });
await gitCheckout("pre/bar", true, { cwd });
await gitCommits(["Seventh"], { cwd });
await gitPush(repositoryUrl, "pre/bar", { cwd });
await gitCheckout("beta", true, { cwd });
await gitCommits(["Eighth"], { cwd });
await gitPush(repositoryUrl, "beta", { cwd });
const branches = [
// Should match all maintenance type branches
{name: '+([0-9])?(.{+([0-9]),x}).x'},
{name: 'master', channel: 'latest'},
{name: 'next'},
{name: 'pre/{foo,bar}', channel: `\${name.replace(/^pre\\//g, '')}`, prerelease: true},
{ name: "+([0-9])?(.{+([0-9]),x}).x" },
{ name: "master", channel: "latest" },
{ name: "next" },
{ name: "pre/{foo,bar}", channel: `\${name.replace(/^pre\\//g, '')}`, prerelease: true },
// Should be ignored as there is no matching branches in the repo
{name: 'missing'},
{ name: "missing" },
// Should be ignored as the matching branch in the repo is already matched by `/^pre\\/(\\w+)$/gi`
{name: '*/foo', channel: 'foo', prerelease: 'foo'},
{name: 'beta', channel: `channel-\${name}`, prerelease: true},
{ name: "*/foo", channel: "foo", prerelease: "foo" },
{ name: "beta", channel: `channel-\${name}`, prerelease: true },
];
t.deepEqual(await expand(repositoryUrl, {cwd}, branches), [
{name: '1.0.x'},
{name: '1.x.x'},
{name: '2.x'},
{name: 'master', channel: 'latest'},
{name: 'next'},
{name: 'pre/bar', channel: 'bar', prerelease: true},
{name: 'pre/foo', channel: 'foo', prerelease: true},
{name: 'beta', channel: 'channel-beta', prerelease: true},
t.deepEqual(await expand(repositoryUrl, { cwd }, branches), [
{ name: "1.0.x" },
{ name: "1.x.x" },
{ name: "2.x" },
{ name: "master", channel: "latest" },
{ name: "next" },
{ name: "pre/bar", channel: "bar", prerelease: true },
{ name: "pre/foo", channel: "foo", prerelease: true },
{ name: "beta", channel: "channel-beta", prerelease: true },
]);
});
+100 -97
View File
@@ -1,153 +1,156 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags.js';
import {gitAddNote, gitCheckout, gitCommits, gitRepo, gitTagVersion} from '../helpers/git-utils.js';
import test from "ava";
import getTags from "../../lib/branches/get-tags.js";
import { gitAddNote, gitCheckout, gitCommits, gitRepo, gitTagVersion } from "../helpers/git-utils.js";
test('Get the valid tags', async (t) => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitTagVersion('foo', undefined, {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
commits.push(...(await gitCommits(['Second'], {cwd})));
await gitTagVersion('v1.0.0', undefined, {cwd});
commits.push(...(await gitCommits(['Third'], {cwd})));
await gitTagVersion('v3.0', undefined, {cwd});
commits.push(...(await gitCommits(['Fourth'], {cwd})));
await gitTagVersion('v3.0.0-beta.1', undefined, {cwd});
test("Get the valid tags", async (t) => {
const { cwd } = await gitRepo();
const commits = await gitCommits(["First"], { cwd });
await gitTagVersion("foo", undefined, { cwd });
await gitTagVersion("v2.0.0", undefined, { cwd });
commits.push(...(await gitCommits(["Second"], { cwd })));
await gitTagVersion("v1.0.0", undefined, { cwd });
commits.push(...(await gitCommits(["Third"], { cwd })));
await gitTagVersion("v3.0", undefined, { cwd });
commits.push(...(await gitCommits(["Fourth"], { cwd })));
await gitTagVersion("v3.0.0-beta.1", undefined, { cwd });
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [{name: 'master'}]);
const result = await getTags({ cwd, options: { tagFormat: `v\${version}` } }, [{ name: "master" }]);
t.deepEqual(result, [
{
name: 'master',
name: "master",
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channels: [null]},
{ gitTag: "v1.0.0", version: "1.0.0", channels: [null] },
{ gitTag: "v2.0.0", version: "2.0.0", channels: [null] },
{ gitTag: "v3.0.0-beta.1", version: "3.0.0-beta.1", channels: [null] },
],
},
]);
});
test('Get the valid tags from multiple branches', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.1.0', {cwd});
await gitCheckout('1.x', true, {cwd});
await gitCheckout('master', false, {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v3.0.0', {cwd});
test("Get the valid tags from multiple branches", async (t) => {
const { cwd } = await gitRepo();
await gitCommits(["First"], { cwd });
await gitTagVersion("v1.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "1.x"] }), "v1.0.0", { cwd });
await gitCommits(["Second"], { cwd });
await gitTagVersion("v1.1.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "1.x"] }), "v1.1.0", { cwd });
await gitCheckout("1.x", true, { cwd });
await gitCheckout("master", false, { cwd });
await gitCommits(["Third"], { cwd });
await gitTagVersion("v2.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v2.0.0", { cwd });
await gitCheckout("next", true, { cwd });
await gitCommits(["Fourth"], { cwd });
await gitTagVersion("v3.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: ["next"] }), "v3.0.0", { cwd });
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [
{name: '1.x'},
{name: 'master'},
{name: 'next'},
const result = await getTags({ cwd, options: { tagFormat: `v\${version}` } }, [
{ name: "1.x" },
{ name: "master" },
{ name: "next" },
]);
t.deepEqual(result, [
{
name: '1.x',
name: "1.x",
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, '1.x']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null, '1.x']},
{ gitTag: "v1.0.0", version: "1.0.0", channels: [null, "1.x"] },
{ gitTag: "v1.1.0", version: "1.1.0", channels: [null, "1.x"] },
],
},
{
name: 'master',
tags: [...result[0].tags, {gitTag: 'v2.0.0', version: '2.0.0', channels: [null, 'next']}],
name: "master",
tags: [...result[0].tags, { gitTag: "v2.0.0", version: "2.0.0", channels: [null, "next"] }],
},
{
name: 'next',
tags: [...result[1].tags, {gitTag: 'v3.0.0', version: '3.0.0', channels: ['next']}],
name: "next",
tags: [...result[1].tags, { gitTag: "v3.0.0", version: "3.0.0", channels: ["next"] }],
},
]);
});
test('Return branches with and empty tags array if no valid tag is found', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitTagVersion('foo', undefined, {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v2.0.x', undefined, {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v3.0', undefined, {cwd});
test("Return branches with and empty tags array if no valid tag is found", async (t) => {
const { cwd } = await gitRepo();
await gitCommits(["First"], { cwd });
await gitTagVersion("foo", undefined, { cwd });
await gitCommits(["Second"], { cwd });
await gitTagVersion("v2.0.x", undefined, { cwd });
await gitCommits(["Third"], { cwd });
await gitTagVersion("v3.0", undefined, { cwd });
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
const result = await getTags({ cwd, options: { tagFormat: `prefix@v\${version}` } }, [{ name: "master" }]);
t.deepEqual(result, [{name: 'master', tags: []}]);
t.deepEqual(result, [{ name: "master", tags: [] }]);
});
test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v3.0.0', {cwd});
await gitCheckout('master', false, {cwd});
test("Return branches with and empty tags array if no valid tag is found in history of configured branches", async (t) => {
const { cwd } = await gitRepo();
await gitCommits(["First"], { cwd });
await gitCheckout("next", true, { cwd });
await gitCommits(["Second"], { cwd });
await gitTagVersion("v1.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v1.0.0", { cwd });
await gitCommits(["Third"], { cwd });
await gitTagVersion("v2.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v2.0.0", { cwd });
await gitCommits(["Fourth"], { cwd });
await gitTagVersion("v3.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v3.0.0", { cwd });
await gitCheckout("master", false, { cwd });
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
const result = await getTags({ cwd, options: { tagFormat: `prefix@v\${version}` } }, [
{ name: "master" },
{ name: "next" },
]);
t.deepEqual(result, [
{name: 'master', tags: []},
{name: 'next', tags: []},
{ name: "master", tags: [] },
{ name: "next", tags: [] },
]);
});
test('Get the highest valid tag corresponding to the "tagFormat"', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
const { cwd } = await gitRepo();
await gitCommits(["First"], { cwd });
await gitTagVersion('1.0.0', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channels: [null]}]},
await gitTagVersion("1.0.0", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `\${version}` } }, [{ name: "master" }]), [
{ name: "master", tags: [{ gitTag: "1.0.0", version: "1.0.0", channels: [null] }] },
]);
await gitTagVersion('foo-1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-\${version}-bar`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channels: [null]}]},
await gitTagVersion("foo-1.0.0-bar", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `foo-\${version}-bar` } }, [{ name: "master" }]), [
{ name: "master", tags: [{ gitTag: "foo-1.0.0-bar", version: "1.0.0", channels: [null] }] },
]);
await gitTagVersion('foo-v1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-v\${version}-bar`}}, [{name: 'master'}]), [
await gitTagVersion("foo-v1.0.0-bar", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `foo-v\${version}-bar` } }, [{ name: "master" }]), [
{
name: 'master',
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channels: [null]}],
name: "master",
tags: [{ gitTag: "foo-v1.0.0-bar", version: "1.0.0", channels: [null] }],
},
]);
await gitTagVersion('(.+)/1.0.0/(a-z)', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `(.+)/\${version}/(a-z)`}}, [{name: 'master'}]), [
await gitTagVersion("(.+)/1.0.0/(a-z)", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `(.+)/\${version}/(a-z)` } }, [{ name: "master" }]), [
{
name: 'master',
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channels: [null]}],
name: "master",
tags: [{ gitTag: "(.+)/1.0.0/(a-z)", version: "1.0.0", channels: [null] }],
},
]);
await gitTagVersion('2.0.0-1.0.0-bar.1', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `2.0.0-\${version}-bar.1`}}, [{name: 'master'}]), [
await gitTagVersion("2.0.0-1.0.0-bar.1", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `2.0.0-\${version}-bar.1` } }, [{ name: "master" }]), [
{
name: 'master',
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channels: [null]}],
name: "master",
tags: [{ gitTag: "2.0.0-1.0.0-bar.1", version: "1.0.0", channels: [null] }],
},
]);
await gitTagVersion('3.0.0-bar.2', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}-bar.2`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channels: [null]}]},
await gitTagVersion("3.0.0-bar.2", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `\${version}-bar.2` } }, [{ name: "master" }]), [
{ name: "master", tags: [{ gitTag: "3.0.0-bar.2", version: "3.0.0", channels: [null] }] },
]);
});
+193 -191
View File
@@ -1,17 +1,17 @@
import test from 'ava';
import * as normalize from '../../lib/branches/normalize.js';
import test from "ava";
import * as normalize from "../../lib/branches/normalize.js";
const toTags = (versions) => versions.map((version) => ({version}));
const toTags = (versions) => versions.map((version) => ({ version }));
test('Maintenance branches - initial state', (t) => {
test("Maintenance branches - initial state", (t) => {
const maintenance = [
{name: '1.x', channel: '1.x', tags: []},
{name: '1.1.x', tags: []},
{name: '1.2.x', tags: []},
{ name: "1.x", channel: "1.x", tags: [] },
{ name: "1.1.x", tags: [] },
{ name: "1.2.x", tags: [] },
];
const release = [{name: 'master', tags: []}];
const release = [{ name: "master", tags: [] }];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
normalize.maintenance({ maintenance, release }).map(({ type, name, range, accept, channel, mergeRange }) => ({
type,
name,
range,
@@ -21,51 +21,51 @@ test('Maintenance branches - initial state', (t) => {
})),
[
{
type: 'maintenance',
name: '1.1.x',
range: '>=1.1.0 <1.0.0',
type: "maintenance",
name: "1.1.x",
range: ">=1.1.0 <1.0.0",
accept: [],
channel: '1.1.x',
mergeRange: '>=1.1.0 <1.2.0',
channel: "1.1.x",
mergeRange: ">=1.1.0 <1.2.0",
},
{
type: 'maintenance',
name: '1.2.x',
range: '>=1.2.0 <1.0.0',
type: "maintenance",
name: "1.2.x",
range: ">=1.2.0 <1.0.0",
accept: [],
channel: '1.2.x',
mergeRange: '>=1.2.0 <1.3.0',
channel: "1.2.x",
mergeRange: ">=1.2.0 <1.3.0",
},
{
type: 'maintenance',
name: '1.x',
range: '>=1.3.0 <1.0.0',
type: "maintenance",
name: "1.x",
range: ">=1.3.0 <1.0.0",
accept: [],
channel: '1.x',
mergeRange: '>=1.3.0 <2.0.0',
channel: "1.x",
mergeRange: ">=1.3.0 <2.0.0",
},
]
);
});
test('Maintenance branches - cap range to first release present on default branch and not in any Maintenance one', (t) => {
test("Maintenance branches - cap range to first release present on default branch and not in any Maintenance one", (t) => {
const maintenance = [
{name: '1.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0'])},
{name: 'name', range: '1.1.x', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1'])},
{name: '1.2.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1'])},
{name: '2.x.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.5.0'])},
{ name: "1.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1", "1.3.0", "1.4.0", "1.5.0"]) },
{ name: "name", range: "1.1.x", tags: toTags(["1.0.0", "1.0.1", "1.1.0", "1.1.1"]) },
{ name: "1.2.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1"]) },
{ name: "2.x.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1", "1.5.0"]) },
];
const release = [
{
name: 'master',
tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0', '1.6.0', '2.0.0']),
name: "master",
tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1", "1.3.0", "1.4.0", "1.5.0", "1.6.0", "2.0.0"]),
},
];
t.deepEqual(
normalize
.maintenance({maintenance, release})
.map(({type, name, range, accept, channel, mergeRange: maintenanceRange}) => ({
.maintenance({ maintenance, release })
.map(({ type, name, range, accept, channel, mergeRange: maintenanceRange }) => ({
type,
name,
range,
@@ -75,50 +75,50 @@ test('Maintenance branches - cap range to first release present on default branc
})),
[
{
type: 'maintenance',
name: 'name',
range: '>=1.1.1 <1.2.0',
accept: ['patch'],
channel: 'name',
mergeRange: '>=1.1.0 <1.2.0',
type: "maintenance",
name: "name",
range: ">=1.1.1 <1.2.0",
accept: ["patch"],
channel: "name",
mergeRange: ">=1.1.0 <1.2.0",
},
{
type: 'maintenance',
name: '1.2.x',
range: '>=1.2.1 <1.3.0',
accept: ['patch'],
channel: '1.2.x',
mergeRange: '>=1.2.0 <1.3.0',
type: "maintenance",
name: "1.2.x",
range: ">=1.2.1 <1.3.0",
accept: ["patch"],
channel: "1.2.x",
mergeRange: ">=1.2.0 <1.3.0",
},
{
type: 'maintenance',
name: '1.x',
range: '>=1.5.0 <1.6.0',
accept: ['patch'],
channel: '1.x',
mergeRange: '>=1.3.0 <2.0.0',
type: "maintenance",
name: "1.x",
range: ">=1.5.0 <1.6.0",
accept: ["patch"],
channel: "1.x",
mergeRange: ">=1.3.0 <2.0.0",
},
{
type: 'maintenance',
name: '2.x.x',
range: '>=2.0.0 <1.6.0',
type: "maintenance",
name: "2.x.x",
range: ">=2.0.0 <1.6.0",
accept: [],
channel: '2.x.x',
mergeRange: '>=2.0.0 <3.0.0',
channel: "2.x.x",
mergeRange: ">=2.0.0 <3.0.0",
},
]
);
});
test('Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch', (t) => {
test("Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch", (t) => {
const maintenance = [
{name: '1.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0'])},
{name: '2.x.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])},
{ name: "1.x", tags: toTags(["1.0.0", "1.2.0", "1.3.0"]) },
{ name: "2.x.x", tags: toTags(["1.0.0", "1.2.0", "1.3.0", "2.0.0"]) },
];
const release = [{name: 'master', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])}];
const release = [{ name: "master", tags: toTags(["1.0.0", "1.2.0", "1.3.0", "2.0.0"]) }];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
normalize.maintenance({ maintenance, release }).map(({ type, name, range, accept, channel, mergeRange }) => ({
type,
name,
range,
@@ -128,270 +128,272 @@ test('Maintenance branches - cap range to default branch last release if all rel
})),
[
{
type: 'maintenance',
name: '1.x',
range: '>=1.3.0 <2.0.0',
accept: ['patch', 'minor'],
channel: '1.x',
mergeRange: '>=1.0.0 <2.0.0',
type: "maintenance",
name: "1.x",
range: ">=1.3.0 <2.0.0",
accept: ["patch", "minor"],
channel: "1.x",
mergeRange: ">=1.0.0 <2.0.0",
},
{
type: 'maintenance',
name: '2.x.x',
range: '>=2.0.0 <2.0.0',
type: "maintenance",
name: "2.x.x",
range: ">=2.0.0 <2.0.0",
accept: [],
channel: '2.x.x',
mergeRange: '>=2.0.0 <3.0.0',
channel: "2.x.x",
mergeRange: ">=2.0.0 <3.0.0",
},
]
);
});
test('Release branches - initial state', (t) => {
test("Release branches - initial state", (t) => {
const release = [
{name: 'master', tags: []},
{name: 'next', channel: 'next', tags: []},
{name: 'next-major', tags: []},
{ name: "master", tags: [] },
{ name: "next", channel: "next", tags: [] },
{ name: "next-major", tags: [] },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
type: "release",
name: "master",
range: ">=1.0.0",
accept: ["patch", "minor", "major"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=1.0.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=1.0.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
test('Release branches - 3 release branches', (t) => {
test("Release branches - 3 release branches", (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.0.2'])},
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0'])},
{name: 'next-major', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
{ name: "master", tags: toTags(["1.0.0", "1.0.1", "1.0.2"]) },
{ name: "next", tags: toTags(["1.0.0", "1.0.1", "1.0.2", "1.1.0", "1.2.0"]) },
{ name: "next-major", tags: toTags(["1.0.0", "1.0.1", "1.0.2", "1.1.0", "1.2.0", "2.0.0", "2.0.1", "2.1.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined, main: true},
{ type: "release", name: "master", range: ">=1.0.2 <1.1.0", accept: ["patch"], channel: undefined, main: true },
{
type: 'release',
name: 'next',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
channel: 'next',
type: "release",
name: "next",
range: ">=1.2.0 <2.0.0",
accept: ["patch", "minor"],
channel: "next",
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=2.1.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
test('Release branches - 2 release branches', (t) => {
test("Release branches - 2 release branches", (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
{ name: "master", tags: toTags(["1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0"]) },
{ name: "next", tags: toTags(["1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0", "2.0.0", "2.0.1", "2.1.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
type: "release",
name: "master",
range: ">=1.2.0 <2.0.0",
accept: ["patch", "minor"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=2.1.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
]
);
});
test('Release branches - 1 release branches', (t) => {
const release = [{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0'])}];
test("Release branches - 1 release branches", (t) => {
const release = [{ name: "master", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0"]) }];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
[{type: 'release', name: 'master', range: '>=1.2.0', accept: ['patch', 'minor', 'major'], channel: undefined}]
normalize
.release({ release })
.map(({ type, name, range, accept, channel }) => ({ type, name, range, accept, channel })),
[{ type: "release", name: "master", range: ">=1.2.0", accept: ["patch", "minor", "major"], channel: undefined }]
);
});
test('Release branches - cap ranges to first release only present on following branch', (t) => {
test("Release branches - cap ranges to first release only present on following branch", (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0'])},
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0', '2.2.0'])},
{ name: "master", tags: toTags(["1.0.0", "1.1.0", "1.2.0", "2.0.0"]) },
{ name: "next", tags: toTags(["1.0.0", "1.1.0", "1.2.0", "2.0.0", "2.1.0"]) },
{ name: "next-major", tags: toTags(["1.0.0", "1.1.0", "1.2.0", "2.0.0", "2.1.0", "2.2.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined, main: true},
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next', main: false},
{ type: "release", name: "master", range: ">=2.0.0 <2.1.0", accept: ["patch"], channel: undefined, main: true },
{ type: "release", name: "next", range: ">=2.1.0 <2.2.0", accept: ["patch"], channel: "next", main: false },
{
type: 'release',
name: 'next-major',
range: '>=2.2.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=2.2.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
test('Release branches - Handle missing previous tags in branch history', (t) => {
test("Release branches - Handle missing previous tags in branch history", (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '2.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '2.0.0'])},
{ name: "master", tags: toTags(["1.0.0", "2.0.0"]) },
{ name: "next", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "2.0.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
type: "release",
name: "master",
range: ">=2.0.0",
accept: ["patch", "minor", "major"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=2.0.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
]
);
});
test('Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release', (t) => {
test("Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release", (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0', '3.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0'])},
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
{ name: "master", tags: toTags(["1.0.0", "1.1.0", "2.0.0", "3.0.0"]) },
{ name: "next", tags: toTags(["1.0.0", "1.1.0"]) },
{ name: "next-major", tags: toTags(["1.0.0", "1.1.0", "2.0.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
type: "release",
name: "master",
range: ">=3.0.0",
accept: ["patch", "minor", "major"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=3.0.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=3.0.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
test('Prerelease branches', (t) => {
test("Prerelease branches", (t) => {
const prerelease = [
{name: 'beta', channel: 'beta', prerelease: true, tags: []},
{name: 'alpha', prerelease: 'preview', tags: []},
{ name: "beta", channel: "beta", prerelease: true, tags: [] },
{ name: "alpha", prerelease: "preview", tags: [] },
];
t.deepEqual(
normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})),
normalize.prerelease({ prerelease }).map(({ type, name, channel }) => ({ type, name, channel })),
[
{type: 'prerelease', name: 'beta', channel: 'beta'},
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
{ type: "prerelease", name: "beta", channel: "beta" },
{ type: "prerelease", name: "alpha", channel: "alpha" },
]
);
});
test('Allow to set channel to "false" to prevent default', (t) => {
const maintenance = [{name: '1.x', channel: false, tags: []}];
const maintenance = [{ name: "1.x", channel: false, tags: [] }];
const release = [
{name: 'master', channel: false, tags: []},
{name: 'next', channel: false, tags: []},
{ name: "master", channel: false, tags: [] },
{ name: "next", channel: false, tags: [] },
];
const prerelease = [{name: 'beta', channel: false, prerelease: true, tags: []}];
const prerelease = [{ name: "beta", channel: false, prerelease: true, tags: [] }];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})),
[{name: '1.x', channel: false}]
normalize.maintenance({ maintenance, release }).map(({ name, channel }) => ({ name, channel })),
[{ name: "1.x", channel: false }]
);
t.deepEqual(
normalize.release({release}).map(({name, channel}) => ({name, channel})),
normalize.release({ release }).map(({ name, channel }) => ({ name, channel })),
[
{name: 'master', channel: false},
{name: 'next', channel: false},
{ name: "master", channel: false },
{ name: "next", channel: false },
]
);
t.deepEqual(
normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})),
[{name: 'beta', channel: false}]
normalize.prerelease({ prerelease }).map(({ name, channel }) => ({ name, channel })),
[{ name: "beta", channel: false }]
);
});
+56 -54
View File
@@ -1,92 +1,94 @@
import test from 'ava';
import {maintenance, prerelease, release} from '../../lib/definitions/branches.js';
import test from "ava";
import { maintenance, prerelease, release } from "../../lib/definitions/branches.js";
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
/* eslint-disable unicorn/no-fn-reference-in-iterator */
t.true(maintenance.filter({name: '1.x.x'}));
t.true(maintenance.filter({name: '1.0.x'}));
t.true(maintenance.filter({name: '1.x'}));
t.true(maintenance.filter({name: 'some-name', range: '1.x.x'}));
t.true(maintenance.filter({name: 'some-name', range: '1.1.x'}));
t.true(maintenance.filter({name: 'some-name', range: ''}));
t.true(maintenance.filter({name: 'some-name', range: true}));
t.true(maintenance.filter({ name: "1.x.x" }));
t.true(maintenance.filter({ name: "1.0.x" }));
t.true(maintenance.filter({ name: "1.x" }));
t.true(maintenance.filter({ name: "some-name", range: "1.x.x" }));
t.true(maintenance.filter({ name: "some-name", range: "1.1.x" }));
t.true(maintenance.filter({ name: "some-name", range: "" }));
t.true(maintenance.filter({ name: "some-name", range: true }));
t.false(maintenance.filter({name: 'some-name', range: null}));
t.false(maintenance.filter({name: 'some-name', range: false}));
t.false(maintenance.filter({name: 'some-name'}));
t.false(maintenance.filter({name: '1.0.0'}));
t.false(maintenance.filter({name: 'x.x.x'}));
t.false(maintenance.filter({ name: "some-name", range: null }));
t.false(maintenance.filter({ name: "some-name", range: false }));
t.false(maintenance.filter({ name: "some-name" }));
t.false(maintenance.filter({ name: "1.0.0" }));
t.false(maintenance.filter({ name: "x.x.x" }));
/* eslint-enable unicorn/no-fn-reference-in-iterator */
});
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
t.true(maintenance.branchValidator({name: 'some-name', range: '1.x.x'}));
t.true(maintenance.branchValidator({name: 'some-name', range: '1.1.x'}));
t.true(maintenance.branchValidator({ name: "some-name", range: "1.x.x" }));
t.true(maintenance.branchValidator({ name: "some-name", range: "1.1.x" }));
t.false(maintenance.branchValidator({name: 'some-name', range: '^1.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: '>=1.0.0 <2.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: '1.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: 'wrong-range'}));
t.false(maintenance.branchValidator({name: 'some-name', range: true}));
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
t.false(maintenance.branchValidator({ name: "some-name", range: "^1.0.0" }));
t.false(maintenance.branchValidator({ name: "some-name", range: ">=1.0.0 <2.0.0" }));
t.false(maintenance.branchValidator({ name: "some-name", range: "1.0.0" }));
t.false(maintenance.branchValidator({ name: "some-name", range: "wrong-range" }));
t.false(maintenance.branchValidator({ name: "some-name", range: true }));
t.false(maintenance.branchValidator({ name: "some-name", range: "" }));
});
test('The "maintenance" branches must have unique ranges', (t) => {
t.true(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.0.x'}]));
t.true(maintenance.branchesValidator([{ range: "1.x.x" }, { range: "1.0.x" }]));
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x.x'}]));
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
t.false(maintenance.branchesValidator([{ range: "1.x.x" }, { range: "1.x.x" }]));
t.false(maintenance.branchesValidator([{ range: "1.x.x" }, { range: "1.x" }]));
});
test('A "prerelease" branch is identified by having a thruthy "prerelease" property', (t) => {
/* eslint-disable unicorn/no-fn-reference-in-iterator */
t.true(prerelease.filter({name: 'some-name', prerelease: true}));
t.true(prerelease.filter({name: 'some-name', prerelease: 'beta'}));
t.true(prerelease.filter({name: 'some-name', prerelease: ''}));
t.true(prerelease.filter({ name: "some-name", prerelease: true }));
t.true(prerelease.filter({ name: "some-name", prerelease: "beta" }));
t.true(prerelease.filter({ name: "some-name", prerelease: "" }));
t.false(prerelease.filter({name: 'some-name', prerelease: null}));
t.false(prerelease.filter({name: 'some-name', prerelease: false}));
t.false(prerelease.filter({name: 'some-name'}));
t.false(prerelease.filter({ name: "some-name", prerelease: null }));
t.false(prerelease.filter({ name: "some-name", prerelease: false }));
t.false(prerelease.filter({ name: "some-name" }));
/* eslint-enable unicorn/no-fn-reference-in-iterator */
});
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', (t) => {
t.true(prerelease.branchValidator({name: 'beta', prerelease: true}));
t.true(prerelease.branchValidator({name: 'some-name', prerelease: 'beta'}));
t.true(prerelease.branchValidator({ name: "beta", prerelease: true }));
t.true(prerelease.branchValidator({ name: "some-name", prerelease: "beta" }));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: ''}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: null}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: false}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '000'}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '#beta'}));
t.false(prerelease.branchValidator({name: '000', prerelease: true}));
t.false(prerelease.branchValidator({name: '#beta', prerelease: true}));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "" }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: null }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: false }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "000" }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "#beta" }));
t.false(prerelease.branchValidator({ name: "000", prerelease: true }));
t.false(prerelease.branchValidator({ name: "#beta", prerelease: true }));
});
test('The "prerelease" branches must have unique "prerelease" property', (t) => {
t.true(prerelease.branchesValidator([{prerelease: 'beta'}, {prerelease: 'alpha'}]));
t.true(prerelease.branchesValidator([{ prerelease: "beta" }, { prerelease: "alpha" }]));
t.false(prerelease.branchesValidator([{range: 'beta'}, {range: 'beta'}, {range: 'alpha'}]));
t.false(prerelease.branchesValidator([{ range: "beta" }, { range: "beta" }, { range: "alpha" }]));
});
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
/* eslint-disable unicorn/no-fn-reference-in-iterator */
t.true(release.filter({name: 'some-name'}));
t.true(release.filter({ name: "some-name" }));
t.false(release.filter({name: '1.x.x'}));
t.false(release.filter({name: '1.0.x'}));
t.false(release.filter({name: 'some-name', range: '1.x.x'}));
t.false(release.filter({name: 'some-name', range: '1.1.x'}));
t.false(release.filter({name: 'some-name', prerelease: true}));
t.false(release.filter({name: 'some-name', prerelease: 'beta'}));
t.false(release.filter({ name: "1.x.x" }));
t.false(release.filter({ name: "1.0.x" }));
t.false(release.filter({ name: "some-name", range: "1.x.x" }));
t.false(release.filter({ name: "some-name", range: "1.1.x" }));
t.false(release.filter({ name: "some-name", prerelease: true }));
t.false(release.filter({ name: "some-name", prerelease: "beta" }));
/* eslint-enable unicorn/no-fn-reference-in-iterator */
});
test('There must be between 1 and 3 release branches', (t) => {
t.true(release.branchesValidator([{name: 'branch1'}]));
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}]));
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}]));
test("There must be between 1 and 3 release branches", (t) => {
t.true(release.branchesValidator([{ name: "branch1" }]));
t.true(release.branchesValidator([{ name: "branch1" }, { name: "branch2" }]));
t.true(release.branchesValidator([{ name: "branch1" }, { name: "branch2" }, { name: "branch3" }]));
t.false(release.branchesValidator([]));
t.false(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}, {name: 'branch4'}]));
t.false(
release.branchesValidator([{ name: "branch1" }, { name: "branch2" }, { name: "branch3" }, { name: "branch4" }])
);
});
+24 -24
View File
@@ -1,15 +1,15 @@
import test from 'ava';
import plugins from '../../lib/definitions/plugins.js';
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants.js';
import test from "ava";
import plugins from "../../lib/definitions/plugins.js";
import { RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT } from "../../lib/definitions/constants.js";
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', (t) => {
t.false(plugins.analyzeCommits.outputValidator('invalid'));
t.false(plugins.analyzeCommits.outputValidator("invalid"));
t.false(plugins.analyzeCommits.outputValidator(1));
t.false(plugins.analyzeCommits.outputValidator({}));
t.true(plugins.analyzeCommits.outputValidator());
t.true(plugins.analyzeCommits.outputValidator(null));
t.true(plugins.analyzeCommits.outputValidator('major'));
t.true(plugins.analyzeCommits.outputValidator("major"));
});
test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
@@ -18,57 +18,57 @@ test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
t.true(plugins.generateNotes.outputValidator());
t.true(plugins.generateNotes.outputValidator(null));
t.true(plugins.generateNotes.outputValidator(''));
t.true(plugins.generateNotes.outputValidator('string'));
t.true(plugins.generateNotes.outputValidator(""));
t.true(plugins.generateNotes.outputValidator("string"));
});
test('The "publish" plugin output, if defined, must be an object or "false"', (t) => {
t.false(plugins.publish.outputValidator(1));
t.false(plugins.publish.outputValidator('string'));
t.false(plugins.publish.outputValidator("string"));
t.true(plugins.publish.outputValidator({}));
t.true(plugins.publish.outputValidator());
t.true(plugins.publish.outputValidator(null));
t.true(plugins.publish.outputValidator(''));
t.true(plugins.publish.outputValidator(""));
t.true(plugins.publish.outputValidator(false));
});
test('The "addChannel" plugin output, if defined, must be an object', (t) => {
t.false(plugins.addChannel.outputValidator(1));
t.false(plugins.addChannel.outputValidator('string'));
t.false(plugins.addChannel.outputValidator("string"));
t.true(plugins.addChannel.outputValidator({}));
t.true(plugins.addChannel.outputValidator());
t.true(plugins.addChannel.outputValidator(null));
t.true(plugins.addChannel.outputValidator(''));
t.true(plugins.addChannel.outputValidator(""));
});
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', (t) => {
const env = {MY_TOKEN: 'secret token'};
t.is(plugins.generateNotes.postprocess(['note 1', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
t.is(plugins.generateNotes.postprocess(['', 'note'], {env}), 'note');
t.is(plugins.generateNotes.postprocess([undefined, 'note'], {env}), 'note');
t.is(plugins.generateNotes.postprocess(['note 1', '', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
const env = { MY_TOKEN: "secret token" };
t.is(plugins.generateNotes.postprocess(["note 1", "note 2"], { env }), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
t.is(plugins.generateNotes.postprocess(["", "note"], { env }), "note");
t.is(plugins.generateNotes.postprocess([undefined, "note"], { env }), "note");
t.is(plugins.generateNotes.postprocess(["note 1", "", "note 2"], { env }), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
t.is(
plugins.generateNotes.postprocess(['note 1', undefined, 'note 2'], {env}),
plugins.generateNotes.postprocess(["note 1", undefined, "note 2"], { env }),
`note 1${RELEASE_NOTES_SEPARATOR}note 2`
);
t.is(
plugins.generateNotes.postprocess(
[`Note 1: Exposing token ${env.MY_TOKEN}`, `Note 2: Exposing token ${SECRET_REPLACEMENT}`],
{env}
{ env }
),
`Note 1: Exposing token ${SECRET_REPLACEMENT}${RELEASE_NOTES_SEPARATOR}Note 2: Exposing token ${SECRET_REPLACEMENT}`
);
});
test('The "analyzeCommits" plugins output are reduced to the highest release type', (t) => {
t.is(plugins.analyzeCommits.postprocess(['major', 'minor']), 'major');
t.is(plugins.analyzeCommits.postprocess(['', 'minor']), 'minor');
t.is(plugins.analyzeCommits.postprocess([undefined, 'patch']), 'patch');
t.is(plugins.analyzeCommits.postprocess([null, 'patch']), 'patch');
t.is(plugins.analyzeCommits.postprocess(['wrong_type', 'minor']), 'minor');
t.is(plugins.analyzeCommits.postprocess(["major", "minor"]), "major");
t.is(plugins.analyzeCommits.postprocess(["", "minor"]), "minor");
t.is(plugins.analyzeCommits.postprocess([undefined, "patch"]), "patch");
t.is(plugins.analyzeCommits.postprocess([null, "patch"]), "patch");
t.is(plugins.analyzeCommits.postprocess(["wrong_type", "minor"]), "minor");
t.is(plugins.analyzeCommits.postprocess([]), undefined);
t.is(plugins.analyzeCommits.postprocess(['wrong_type']), undefined);
t.is(plugins.analyzeCommits.postprocess(["wrong_type"]), undefined);
});
+1 -1
View File
@@ -1 +1 @@
export default () => {}
export default () => {};
+3 -3
View File
@@ -1,4 +1,4 @@
import SemanticReleaseError from '@semantic-release/error';
import SemanticReleaseError from "@semantic-release/error";
class InheritedError extends SemanticReleaseError {
constructor(message, code) {
@@ -10,5 +10,5 @@ class InheritedError extends SemanticReleaseError {
}
export default () => {
throw new InheritedError('Inherited error', 'EINHERITED');
}
throw new InheritedError("Inherited error", "EINHERITED");
};
+3 -3
View File
@@ -1,5 +1,5 @@
export default () => {
const error = new Error('a');
error.errorProperty = 'errorProperty';
const error = new Error("a");
error.errorProperty = "errorProperty";
throw error;
}
};
+3 -3
View File
@@ -1,5 +1,5 @@
import AggregateError from 'aggregate-error';
import AggregateError from "aggregate-error";
export default () => {
throw new AggregateError([new Error('a'), new Error('b')]);
}
throw new AggregateError([new Error("a"), new Error("b")]);
};
+3
View File
@@ -0,0 +1,3 @@
export async function verifyConditions(pluginConfig, context) {
context.logger.log("verifyConditions called");
}
+1 -1
View File
@@ -1 +1 @@
export default (pluginConfig, context) => context
export default (pluginConfig, context) => context;
+2 -2
View File
@@ -1,6 +1,6 @@
export default (pluginConfig, {env, logger}) => {
export default (pluginConfig, { env, logger }) => {
console.log(`Console: Exposing token ${env.MY_TOKEN}`);
logger.log(`Log: Exposing token ${env.MY_TOKEN}`);
logger.error(`Error: Console token ${env.MY_TOKEN}`);
throw new Error(`Throw error: Exposing ${env.MY_TOKEN}`);
}
};
+1 -1
View File
@@ -1 +1 @@
export default (pluginConfig, context) => ({pluginConfig, context})
export default (pluginConfig, context) => ({ pluginConfig, context });
+59 -52
View File
@@ -1,10 +1,10 @@
import {temporaryDirectory} from 'tempy';
import {execa} from 'execa';
import fileUrl from 'file-url';
import pEachSeries from 'p-each-series';
import gitLogParser from 'git-log-parser';
import getStream from 'get-stream';
import {GIT_NOTE_REF} from '../../lib/definitions/constants.js';
import { temporaryDirectory } from "tempy";
import { execa } from "execa";
import fileUrl from "file-url";
import pEachSeries from "p-each-series";
import gitLogParser from "git-log-parser";
import getStream from "get-stream";
import { GIT_NOTE_REF } from "../../lib/definitions/constants.js";
/**
* Commit message information.
@@ -25,14 +25,14 @@ import {GIT_NOTE_REF} from '../../lib/definitions/constants.js';
*/
export async function initGit(withRemote) {
const cwd = temporaryDirectory();
const args = withRemote ? ['--bare', '--initial-branch=master'] : ['--initial-branch=master'];
const args = withRemote ? ["--bare", "--initial-branch=master"] : ["--initial-branch=master"];
await execa('git', ['init', ...args], {cwd}).catch(() => {
const args = withRemote ? ['--bare'] : [];
return execa('git', ['init', ...args], {cwd});
await execa("git", ["init", ...args], { cwd }).catch(() => {
const args = withRemote ? ["--bare"] : [];
return execa("git", ["init", ...args], { cwd });
});
const repositoryUrl = fileUrl(cwd);
return {cwd, repositoryUrl};
return { cwd, repositoryUrl };
}
/**
@@ -45,18 +45,18 @@ export async function initGit(withRemote) {
* @param {String} [branch='master'] The branch to initialize.
* @return {String} The path of the clone if `withRemote` is `true`, the path of the repository otherwise.
*/
export async function gitRepo(withRemote, branch = 'master') {
let {cwd, repositoryUrl} = await initGit(withRemote);
export async function gitRepo(withRemote, branch = "master") {
let { cwd, repositoryUrl } = await initGit(withRemote);
if (withRemote) {
await initBareRepo(repositoryUrl, branch);
cwd = await gitShallowClone(repositoryUrl, branch);
} else {
await gitCheckout(branch, true, {cwd});
await gitCheckout(branch, true, { cwd });
}
await execa('git', ['config', 'commit.gpgsign', false], {cwd});
await execa("git", ["config", "commit.gpgsign", false], { cwd });
return {cwd, repositoryUrl};
return { cwd, repositoryUrl };
}
/**
@@ -70,12 +70,12 @@ export async function gitRepo(withRemote, branch = 'master') {
* @param {String} repositoryUrl The URL of the bare repository.
* @param {String} [branch='master'] the branch to initialize.
*/
export async function initBareRepo(repositoryUrl, branch = 'master') {
export async function initBareRepo(repositoryUrl, branch = "master") {
const cwd = temporaryDirectory();
await execa('git', ['clone', '--no-hardlinks', repositoryUrl, cwd], {cwd});
await gitCheckout(branch, true, {cwd});
await gitCommits(['Initial commit'], {cwd});
await execa('git', ['push', repositoryUrl, branch], {cwd});
await execa("git", ["clone", "--no-hardlinks", repositoryUrl, cwd], { cwd });
await gitCheckout(branch, true, { cwd });
await gitCommits(["Initial commit"], { cwd });
await execa("git", ["push", repositoryUrl, branch], { cwd });
}
/**
@@ -90,7 +90,9 @@ export async function gitCommits(messages, execaOptions) {
await pEachSeries(
messages,
async (message) =>
(await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOptions)).stdout
(
await execa("git", ["commit", "-m", message, "--allow-empty", "--no-gpg-sign"], execaOptions)
).stdout
);
return (await gitGetCommits(undefined, execaOptions)).slice(0, messages.length);
}
@@ -104,12 +106,17 @@ export async function gitCommits(messages, execaOptions) {
* @return {Array<Object>} The list of parsed commits.
*/
export async function gitGetCommits(from, execaOptions) {
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
Object.assign(gitLogParser.fields, {
hash: "H",
message: "B",
gitTags: "d",
committerDate: { key: "ci", type: Date },
});
return (
await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}HEAD`},
{...execaOptions, env: {...process.env, ...execaOptions.env}}
{ _: `${from ? from + ".." : ""}HEAD` },
{ ...execaOptions, env: { ...process.env, ...execaOptions.env } }
)
)
).map((commit) => {
@@ -127,7 +134,7 @@ export async function gitGetCommits(from, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitCheckout(branch, create, execaOptions) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOptions);
await execa("git", create ? ["checkout", "-b", branch] : ["checkout", branch], execaOptions);
}
/**
@@ -137,7 +144,7 @@ export async function gitCheckout(branch, create, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitFetch(repositoryUrl, execaOptions) {
await execa('git', ['fetch', repositoryUrl], execaOptions);
await execa("git", ["fetch", repositoryUrl], execaOptions);
}
/**
@@ -148,7 +155,7 @@ export async function gitFetch(repositoryUrl, execaOptions) {
* @return {String} The sha of the head commit in the current git repository.
*/
export async function gitHead(execaOptions) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOptions)).stdout;
return (await execa("git", ["rev-parse", "HEAD"], execaOptions)).stdout;
}
/**
@@ -159,7 +166,7 @@ export async function gitHead(execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitTagVersion(tagName, sha, execaOptions) {
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOptions);
await execa("git", sha ? ["tag", "-f", tagName, sha] : ["tag", tagName], execaOptions);
}
/**
@@ -171,10 +178,10 @@ export async function gitTagVersion(tagName, sha, execaOptions) {
* @param {Number} [depth=1] The number of commit to clone.
* @return {String} The path of the cloned repository.
*/
export async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
export async function gitShallowClone(repositoryUrl, branch = "master", depth = 1) {
const cwd = temporaryDirectory();
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, repositoryUrl, cwd], {
await execa("git", ["clone", "--no-hardlinks", "--no-tags", "-b", branch, "--depth", depth, repositoryUrl, cwd], {
cwd,
});
return cwd;
@@ -190,21 +197,21 @@ export async function gitShallowClone(repositoryUrl, branch = 'master', depth =
export async function gitDetachedHead(repositoryUrl, head) {
const cwd = temporaryDirectory();
await execa('git', ['init'], {cwd});
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
await execa('git', ['fetch', repositoryUrl], {cwd});
await execa('git', ['checkout', head], {cwd});
await execa("git", ["init"], { cwd });
await execa("git", ["remote", "add", "origin", repositoryUrl], { cwd });
await execa("git", ["fetch", repositoryUrl], { cwd });
await execa("git", ["checkout", head], { cwd });
return cwd;
}
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
const cwd = temporaryDirectory();
await execa('git', ['init'], {cwd});
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
await execa('git', ['fetch', '--force', repositoryUrl, `${branch}:remotes/origin/${branch}`], {cwd});
await execa('git', ['reset', '--hard', head], {cwd});
await execa('git', ['checkout', '-q', '-B', branch], {cwd});
await execa("git", ["init"], { cwd });
await execa("git", ["remote", "add", "origin", repositoryUrl], { cwd });
await execa("git", ["fetch", "--force", repositoryUrl, `${branch}:remotes/origin/${branch}`], { cwd });
await execa("git", ["reset", "--hard", head], { cwd });
await execa("git", ["checkout", "-q", "-B", branch], { cwd });
return cwd;
}
@@ -216,7 +223,7 @@ export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddConfig(name, value, execaOptions) {
await execa('git', ['config', '--add', name, value], execaOptions);
await execa("git", ["config", "--add", name, value], execaOptions);
}
/**
@@ -228,7 +235,7 @@ export async function gitAddConfig(name, value, execaOptions) {
* @return {String} The sha of the commit associated with `tagName` on the local repository.
*/
export async function gitTagHead(tagName, execaOptions) {
return (await execa('git', ['rev-list', '-1', tagName], execaOptions)).stdout;
return (await execa("git", ["rev-list", "-1", tagName], execaOptions)).stdout;
}
/**
@@ -241,8 +248,8 @@ export async function gitTagHead(tagName, execaOptions) {
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
*/
export async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOptions)).stdout
.split('\n')
return (await execa("git", ["ls-remote", "--tags", repositoryUrl, tagName], execaOptions)).stdout
.split("\n")
.filter((tag) => Boolean(tag))
.map((tag) => tag.match(/^(?<tag>\S+)/)[1])[0];
}
@@ -256,7 +263,7 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
* @return {String} The tag associatedwith the sha in parameter or `null`.
*/
export async function gitCommitTag(gitHead, execaOptions) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOptions)).stdout;
return (await execa("git", ["describe", "--tags", "--exact-match", gitHead], execaOptions)).stdout;
}
/**
@@ -269,7 +276,7 @@ export async function gitCommitTag(gitHead, execaOptions) {
* @throws {Error} if the push failed.
*/
export async function gitPush(repositoryUrl, branch, execaOptions) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOptions);
await execa("git", ["push", "--tags", repositoryUrl, `HEAD:${branch}`], execaOptions);
}
/**
@@ -279,7 +286,7 @@ export async function gitPush(repositoryUrl, branch, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function merge(ref, execaOptions) {
await execa('git', ['merge', '--no-ff', ref], execaOptions);
await execa("git", ["merge", "--no-ff", ref], execaOptions);
}
/**
@@ -289,7 +296,7 @@ export async function merge(ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function mergeFf(ref, execaOptions) {
await execa('git', ['merge', '--ff', ref], execaOptions);
await execa("git", ["merge", "--ff", ref], execaOptions);
}
/**
@@ -299,7 +306,7 @@ export async function mergeFf(ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function rebase(ref, execaOptions) {
await execa('git', ['rebase', ref], execaOptions);
await execa("git", ["rebase", ref], execaOptions);
}
/**
@@ -310,7 +317,7 @@ export async function rebase(ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddNote(note, ref, execaOptions) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOptions);
await execa("git", ["notes", "--ref", GIT_NOTE_REF, "add", "-m", note, ref], execaOptions);
}
/**
@@ -320,5 +327,5 @@ export async function gitAddNote(note, ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitGetNote(ref, execaOptions) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOptions)).stdout;
return (await execa("git", ["notes", "--ref", GIT_NOTE_REF, "show", ref], execaOptions)).stdout;
}
+14 -14
View File
@@ -1,14 +1,14 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import pRetry from 'p-retry';
import {gitShallowClone, initBareRepo} from './git-utils.js';
import Docker from "dockerode";
import getStream from "get-stream";
import pRetry from "p-retry";
import { gitShallowClone, initBareRepo } from "./git-utils.js";
const IMAGE = 'semanticrelease/docker-gitbox:latest';
const IMAGE = "semanticrelease/docker-gitbox:latest";
const SERVER_PORT = 80;
const HOST_PORT = 2080;
const SERVER_HOST = 'localhost';
const GIT_USERNAME = 'integration';
const GIT_PASSWORD = 'suchsecure';
const SERVER_HOST = "localhost";
const GIT_USERNAME = "integration";
const GIT_PASSWORD = "suchsecure";
const docker = new Docker();
let container;
@@ -23,12 +23,12 @@ export async function start() {
container = await docker.createContainer({
Tty: true,
Image: IMAGE,
PortBindings: {[`${SERVER_PORT}/tcp`]: [{HostPort: `${HOST_PORT}`}]},
PortBindings: { [`${SERVER_PORT}/tcp`]: [{ HostPort: `${HOST_PORT}` }] },
});
await container.start();
const exec = await container.exec({
Cmd: ['ng-auth', '-u', GIT_USERNAME, '-p', GIT_PASSWORD],
Cmd: ["ng-auth", "-u", GIT_USERNAME, "-p", GIT_PASSWORD],
AttachStdout: true,
AttachStderr: true,
});
@@ -51,9 +51,9 @@ export async function stop() {
* @param {String} [description=`Repository ${name}`] The repository description.
* @return {Object} The `repositoryUrl` (URL without auth) and `authUrl` (URL with auth).
*/
export async function createRepo(name, branch = 'master', description = `Repository ${name}`) {
export async function createRepo(name, branch = "master", description = `Repository ${name}`) {
const exec = await container.exec({
Cmd: ['repo-admin', '-n', name, '-d', description],
Cmd: ["repo-admin", "-n", name, "-d", description],
AttachStdout: true,
AttachStderr: true,
});
@@ -63,8 +63,8 @@ export async function createRepo(name, branch = 'master', description = `Reposit
const authUrl = `http://${gitCredential}@${SERVER_HOST}:${HOST_PORT}/git/${name}.git`;
// Retry as the server might take a few ms to make the repo available push
await pRetry(() => initBareRepo(authUrl, branch), {retries: 5, minTimeout: 500, factor: 2});
await pRetry(() => initBareRepo(authUrl, branch), { retries: 5, minTimeout: 500, factor: 2 });
const cwd = await gitShallowClone(authUrl);
return {cwd, repositoryUrl, authUrl};
return { cwd, repositoryUrl, authUrl };
}
+15 -15
View File
@@ -1,12 +1,12 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import pRetry from 'p-retry';
import {mockServerClient} from 'mockserver-client';
import Docker from "dockerode";
import getStream from "get-stream";
import got from "got";
import pRetry from "p-retry";
import { mockServerClient } from "mockserver-client";
const IMAGE = 'mockserver/mockserver:latest';
const IMAGE = "mockserver/mockserver:latest";
const MOCK_SERVER_PORT = 1080;
const MOCK_SERVER_HOST = 'localhost';
const MOCK_SERVER_HOST = "localhost";
const docker = new Docker();
let container;
@@ -19,13 +19,13 @@ export async function start() {
container = await docker.createContainer({
Tty: true,
Image: IMAGE,
PortBindings: {[`${MOCK_SERVER_PORT}/tcp`]: [{HostPort: `${MOCK_SERVER_PORT}`}]},
PortBindings: { [`${MOCK_SERVER_PORT}/tcp`]: [{ HostPort: `${MOCK_SERVER_PORT}` }] },
});
await container.start();
try {
// Wait for the mock server to be ready
await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, {cache: false}), {
await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, { cache: false }), {
retries: 7,
minTimeout: 1000,
factor: 2,
@@ -67,17 +67,17 @@ export const url = `http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}`;
*/
export async function mock(
path,
{body: requestBody, headers: requestHeaders},
{method = 'POST', statusCode = 200, body: responseBody}
{ body: requestBody, headers: requestHeaders },
{ method = "POST", statusCode = 200, body: responseBody }
) {
await client.mockAnyResponse({
httpRequest: {path, method},
httpRequest: { path, method },
httpResponse: {
statusCode,
headers: [{name: 'Content-Type', values: ['application/json; charset=utf-8']}],
headers: [{ name: "Content-Type", values: ["application/json; charset=utf-8"] }],
body: JSON.stringify(responseBody),
},
times: {remainingTimes: 1, unlimited: false},
times: { remainingTimes: 1, unlimited: false },
});
return {
@@ -85,7 +85,7 @@ export async function mock(
path,
headers: requestHeaders,
body: requestBody
? {type: 'JSON', json: JSON.stringify(requestBody), matchType: 'ONLY_MATCHING_FIELDS'}
? { type: "JSON", json: JSON.stringify(requestBody), matchType: "ONLY_MATCHING_FIELDS" }
: undefined,
};
}
+21 -21
View File
@@ -1,17 +1,17 @@
import path, {dirname} from 'node:path';
import {fileURLToPath} from 'node:url';
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import delay from 'delay';
import pRetry from 'p-retry';
import path, { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import Docker from "dockerode";
import getStream from "get-stream";
import got from "got";
import delay from "delay";
import pRetry from "p-retry";
const IMAGE = 'verdaccio/verdaccio:5';
const IMAGE = "verdaccio/verdaccio:5";
const REGISTRY_PORT = 4873;
const REGISTRY_HOST = 'localhost';
const NPM_USERNAME = 'integration';
const NPM_PASSWORD = 'suchsecure';
const NPM_EMAIL = 'integration@test.com';
const REGISTRY_HOST = "localhost";
const NPM_USERNAME = "integration";
const NPM_PASSWORD = "suchsecure";
const NPM_EMAIL = "integration@test.com";
const docker = new Docker();
const __dirname = dirname(fileURLToPath(import.meta.url));
let container, npmToken;
@@ -25,8 +25,8 @@ export async function start() {
container = await docker.createContainer({
Tty: true,
Image: IMAGE,
PortBindings: {[`${REGISTRY_PORT}/tcp`]: [{HostPort: `${REGISTRY_PORT}`}]},
Binds: [`${path.join(__dirname, 'config.yaml')}:/verdaccio/conf/config.yaml`],
PortBindings: { [`${REGISTRY_PORT}/tcp`]: [{ HostPort: `${REGISTRY_PORT}` }] },
Binds: [`${path.join(__dirname, "config.yaml")}:/verdaccio/conf/config.yaml`],
});
await container.start();
@@ -34,7 +34,7 @@ export async function start() {
try {
// Wait for the registry to be ready
await pRetry(() => got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/`, {cache: false}), {
await pRetry(() => got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/`, { cache: false }), {
retries: 7,
minTimeout: 1000,
factor: 2,
@@ -45,24 +45,24 @@ export async function start() {
// Create user
await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/user/org.couchdb.user:${NPM_USERNAME}`, {
method: 'PUT',
method: "PUT",
json: {
_id: `org.couchdb.user:${NPM_USERNAME}`,
name: NPM_USERNAME,
roles: [],
type: 'user',
type: "user",
password: NPM_PASSWORD,
email: NPM_EMAIL,
},
});
// Create token for user
({token: npmToken} = await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/npm/v1/tokens`, {
({ token: npmToken } = await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/npm/v1/tokens`, {
username: NPM_USERNAME,
password: NPM_PASSWORD,
method: 'POST',
headers: {'content-type': 'application/json'},
json: {password: NPM_PASSWORD, readonly: false, cidr_whitelist: []}
method: "POST",
headers: { "content-type": "application/json" },
json: { password: NPM_PASSWORD, readonly: false, cidr_whitelist: [] },
}).json());
}
+2 -2
View File
@@ -1,5 +1,5 @@
import {execa} from 'execa';
import { execa } from "execa";
export async function npmView(packageName, env) {
return JSON.parse((await execa('npm', ['view', packageName, '--json'], {env})).stdout);
return JSON.parse((await execa("npm", ["view", packageName, "--json"], { env })).stdout);
}
+24
View File
@@ -44,6 +44,7 @@ const cli = path.resolve("./bin/semantic-release.js");
const pluginError = path.resolve("./test/fixtures/plugin-error");
const pluginInheritedError = path.resolve("./test/fixtures/plugin-error-inherited");
const pluginLogEnv = path.resolve("./test/fixtures/plugin-log-env");
const pluginEsmNamedExports = path.resolve("./test/fixtures/plugin-esm-named-exports");
test.before(async () => {
await Promise.all([gitbox.start(), npmRegistry.start(), mockServer.start()]);
@@ -713,3 +714,26 @@ test("Use the repository URL as is if none of the given git credentials are vali
dummyUrl
);
});
test("ESM Plugin with named exports", async (t) => {
const packageName = "log-secret";
// Create a git repository, set the current working directory at the root of the repo
t.log("Create git repository");
const { cwd, repositoryUrl } = await gitbox.createRepo(packageName);
await writeJson(path.resolve(cwd, "package.json"), {
name: packageName,
version: "0.0.0-dev",
repository: { url: repositoryUrl },
release: { plugins: [pluginEsmNamedExports] },
});
t.log("$ semantic-release");
const { stdout, stderr } = await execa(cli, [], {
env: { ...env, MY_TOKEN: "secret token" },
cwd,
reject: false,
extendEnv: false,
});
t.regex(stdout, new RegExp(`verifyConditions called`));
});
+117 -102
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {noop} from 'lodash-es';
import {stub} from 'sinon';
import normalize from '../../lib/plugins/normalize.js';
import test from "ava";
import { noop } from "lodash-es";
import { stub } from "sinon";
import normalize from "../../lib/plugins/normalize.js";
const cwd = process.cwd();
@@ -10,7 +10,7 @@ test.beforeEach((t) => {
t.context.log = stub();
t.context.error = stub();
t.context.success = stub();
t.context.stderr = {write: stub()};
t.context.stderr = { write: stub() };
t.context.logger = {
log: t.context.log,
error: t.context.error,
@@ -19,98 +19,108 @@ test.beforeEach((t) => {
};
});
test('Normalize and load plugin from string', async (t) => {
test("Normalize and load plugin from string", async (t) => {
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
'./test/fixtures/plugin-noop.cjs',
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./test/fixtures/plugin-noop.cjs",
{}
);
t.is(plugin.pluginName, './test/fixtures/plugin-noop.cjs');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "./test/fixtures/plugin-noop.cjs");
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop.cjs"']);
});
test('Normalize and load plugin from object', async (t) => {
test("Normalize and load plugin from object", async (t) => {
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'publish',
{path: './test/fixtures/plugin-noop.cjs'},
{ cwd, options: {}, logger: t.context.logger },
"publish",
{ path: "./test/fixtures/plugin-noop.cjs" },
{}
);
t.is(plugin.pluginName, './test/fixtures/plugin-noop.cjs');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "./test/fixtures/plugin-noop.cjs");
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], ['Loaded plugin "publish" from "./test/fixtures/plugin-noop.cjs"']);
});
test('Normalize and load plugin from a base file path', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-noop.cjs', {
'./plugin-noop.cjs': './test/fixtures',
});
test("Normalize and load plugin from a base file path", async (t) => {
const plugin = await normalize(
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./plugin-noop.cjs",
{
"./plugin-noop.cjs": "./test/fixtures",
}
);
t.is(plugin.pluginName, './plugin-noop.cjs');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "./plugin-noop.cjs");
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], [
'Loaded plugin "verifyConditions" from "./plugin-noop.cjs" in shareable config "./test/fixtures"',
]);
});
test('Wrap plugin in a function that add the "pluginName" to the error"', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-error', {
'./plugin-error': './test/fixtures',
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "verifyConditions", "./plugin-error", {
"./plugin-error": "./test/fixtures",
});
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.pluginName, './plugin-error');
t.is(error.pluginName, "./plugin-error");
});
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-errors', {
'./plugin-errors': './test/fixtures',
});
const plugin = await normalize(
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./plugin-errors",
{
"./plugin-errors": "./test/fixtures",
}
);
const errors = [...(await t.throwsAsync(plugin({options: {}}))).errors];
const errors = [...(await t.throwsAsync(plugin({ options: {} }))).errors];
for (const error of errors) {
t.is(error.pluginName, './plugin-errors');
t.is(error.pluginName, "./plugin-errors");
}
});
test('Normalize and load plugin from function', async (t) => {
test("Normalize and load plugin from function", async (t) => {
const pluginFunction = () => {};
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "", pluginFunction, {});
t.is(plugin.pluginName, '[Function: pluginFunction]');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "[Function: pluginFunction]");
t.is(typeof plugin, "function");
});
test('Normalize and load plugin that retuns multiple functions', async (t) => {
test("Normalize and load plugin that retuns multiple functions", async (t) => {
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
'./test/fixtures/multi-plugin.cjs',
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./test/fixtures/multi-plugin.cjs",
{}
);
t.is(typeof plugin, 'function');
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/multi-plugin.cjs"']);
});
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async (t) => {
const analyzeCommits = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'analyzeCommits',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"analyzeCommits",
analyzeCommits,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EANALYZECOMMITSOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EANALYZECOMMITSOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -119,16 +129,16 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async (t) => {
const generateNotes = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'generateNotes',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"generateNotes",
generateNotes,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EGENERATENOTESOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EGENERATENOTESOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -137,16 +147,16 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the
test('Wrap "publish" plugin in a function that validate the output of the plugin', async (t) => {
const publish = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'publish',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"publish",
publish,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EPUBLISHOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EPUBLISHOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -155,16 +165,16 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async (t) => {
const addChannel = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'addChannel',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"addChannel",
addChannel,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EADDCHANNELOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EADDCHANNELOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -172,60 +182,60 @@ test('Wrap "addChannel" plugin in a function that validate the output of the plu
test('Plugin is called with "pluginConfig" (with object definition) and input', async (t) => {
const pluginFunction = stub().resolves();
const pluginConf = {path: pluginFunction, conf: 'confValue'};
const options = {global: 'globalValue'};
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({options: {}, param: 'param'});
const pluginConf = { path: pluginFunction, conf: "confValue" };
const options = { global: "globalValue" };
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
await plugin({ options: {}, param: "param" });
t.true(
pluginFunction.calledWithMatch(
{conf: 'confValue', global: 'globalValue'},
{param: 'param', logger: t.context.logger}
{ conf: "confValue", global: "globalValue" },
{ param: "param", logger: t.context.logger }
)
);
});
test('Plugin is called with "pluginConfig" (with array definition) and input', async (t) => {
const pluginFunction = stub().resolves();
const pluginConf = [pluginFunction, {conf: 'confValue'}];
const options = {global: 'globalValue'};
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({options: {}, param: 'param'});
const pluginConf = [pluginFunction, { conf: "confValue" }];
const options = { global: "globalValue" };
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
await plugin({ options: {}, param: "param" });
t.true(
pluginFunction.calledWithMatch(
{conf: 'confValue', global: 'globalValue'},
{param: 'param', logger: t.context.logger}
{ conf: "confValue", global: "globalValue" },
{ param: "param", logger: t.context.logger }
)
);
});
test('Prevent plugins to modify "pluginConfig"', async (t) => {
const pluginFunction = stub().callsFake((pluginConfig) => {
pluginConfig.conf.subConf = 'otherConf';
pluginConfig.conf.subConf = "otherConf";
});
const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
const options = {globalConf: {globalSubConf: 'originalGlobalConf'}};
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({options: {}});
const pluginConf = { path: pluginFunction, conf: { subConf: "originalConf" } };
const options = { globalConf: { globalSubConf: "originalGlobalConf" } };
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
await plugin({ options: {} });
t.is(pluginConf.conf.subConf, 'originalConf');
t.is(options.globalConf.globalSubConf, 'originalGlobalConf');
t.is(pluginConf.conf.subConf, "originalConf");
t.is(options.globalConf.globalSubConf, "originalGlobalConf");
});
test('Prevent plugins to modify its input', async (t) => {
test("Prevent plugins to modify its input", async (t) => {
const pluginFunction = stub().callsFake((pluginConfig, options) => {
options.param.subParam = 'otherParam';
options.param.subParam = "otherParam";
});
const input = {param: {subParam: 'originalSubParam'}, options: {}};
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
const input = { param: { subParam: "originalSubParam" }, options: {} };
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "", pluginFunction, {});
await plugin(input);
t.is(input.param.subParam, 'originalSubParam');
t.is(input.param.subParam, "originalSubParam");
});
test('Return noop if the plugin is not defined', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger});
test("Return noop if the plugin is not defined", async (t) => {
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger });
t.is(plugin, noop);
});
@@ -233,12 +243,12 @@ test('Return noop if the plugin is not defined', async (t) => {
test('Always pass a defined "pluginConfig" for plugin defined with string', async (t) => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'',
'./test/fixtures/plugin-result-config',
{ cwd, options: {}, logger: t.context.logger },
"",
"./test/fixtures/plugin-result-config",
{}
);
const pluginResult = await plugin({options: {}});
const pluginResult = await plugin({ options: {} });
t.deepEqual(pluginResult.pluginConfig, {});
});
@@ -246,33 +256,38 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn
test('Always pass a defined "pluginConfig" for plugin defined with path', async (t) => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'',
{path: './test/fixtures/plugin-result-config'},
{ cwd, options: {}, logger: t.context.logger },
"",
{ path: "./test/fixtures/plugin-result-config" },
{}
);
const pluginResult = await plugin({options: {}});
const pluginResult = await plugin({ options: {} });
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Throws an error if the plugin return an object without the expected plugin function', async (t) => {
test("Throws an error if the plugin return an object without the expected plugin function", async (t) => {
const error = await t.throwsAsync(() =>
normalize({cwd, options: {}, logger: t.context.logger}, 'nonExistentPlugin', './test/fixtures/multi-plugin.cjs', {})
normalize(
{ cwd, options: {}, logger: t.context.logger },
"nonExistentPlugin",
"./test/fixtures/multi-plugin.cjs",
{}
)
);
t.is(error.code, 'EPLUGIN');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EPLUGIN");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
});
test('Throws an error if the plugin is not found', async (t) => {
test("Throws an error if the plugin is not found", async (t) => {
await t.throwsAsync(
() => normalize({cwd, options: {}, logger: t.context.logger}, 'nonExistentPlugin', 'non-existing-path', {}),
() => normalize({ cwd, options: {}, logger: t.context.logger }, "nonExistentPlugin", "non-existing-path", {}),
{
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
code: "MODULE_NOT_FOUND",
instanceOf: Error,
}
);
+30 -30
View File
@@ -1,9 +1,9 @@
import test from 'ava';
import {stub} from 'sinon';
import AggregateError from 'aggregate-error';
import pipeline from '../../lib/plugins/pipeline.js';
import test from "ava";
import { stub } from "sinon";
import AggregateError from "aggregate-error";
import pipeline from "../../lib/plugins/pipeline.js";
test('Execute each function in series passing the same input', async (t) => {
test("Execute each function in series passing the same input", async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
@@ -25,7 +25,7 @@ test('Execute each function in series passing a transformed input from "getNextI
const step4 = stub().resolves(4);
const getNextInput = (lastResult, result) => lastResult + result;
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(0);
const result = await pipeline([step1, step2, step3, step4], { settleAll: false, getNextInput })(0);
t.deepEqual(result, [1, 2, 3, 4]);
t.true(step1.calledWith(0));
@@ -44,7 +44,7 @@ test('Execute each function in series passing the "lastResult" and "result" to "
const step4 = stub().resolves(4);
const getNextInput = stub().returnsArg(0);
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5);
const result = await pipeline([step1, step2, step3, step4], { settleAll: false, getNextInput })(5);
t.deepEqual(result, [1, 2, 3, 4]);
t.deepEqual(getNextInput.args, [
@@ -63,7 +63,7 @@ test('Execute each function in series calling "transform" to modify the results'
const getNextInput = stub().returnsArg(0);
const transform = stub().callsFake((result) => result + 1);
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
const result = await pipeline([step1, step2, step3, step4], { getNextInput, transform })(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [
@@ -82,7 +82,7 @@ test('Execute each function in series calling "transform" to modify the results
const getNextInput = stub().returnsArg(0);
const transform = stub().callsFake((result) => result + 1);
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
const result = await pipeline([step1, step2, step3, step4], { settleAll: true, getNextInput, transform })(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [
@@ -93,24 +93,24 @@ test('Execute each function in series calling "transform" to modify the results
]);
});
test('Stop execution and throw error if a step rejects', async (t) => {
test("Stop execution and throw error if a step rejects", async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().rejects(new Error('test error'));
const step2 = stub().rejects(new Error("test error"));
const step3 = stub().resolves(3);
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), {
instanceOf: Error,
message: 'test error',
message: "test error",
});
t.is(error.message, 'test error');
t.is(error.message, "test error");
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
t.true(step3.notCalled);
});
test('Throw all errors from the first step throwing an AggregateError', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
test("Throw all errors from the first step throwing an AggregateError", async (t) => {
const error1 = new Error("test error 1");
const error2 = new Error("test error 2");
const step1 = stub().resolves(1);
const step2 = stub().rejects(new AggregateError([error1, error2]));
@@ -124,14 +124,14 @@ test('Throw all errors from the first step throwing an AggregateError', async (t
t.true(step3.notCalled);
});
test('Execute all even if a Promise rejects', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
test("Execute all even if a Promise rejects", async (t) => {
const error1 = new Error("test error 1");
const error2 = new Error("test error 2");
const step1 = stub().resolves(1);
const step2 = stub().rejects(error1);
const step3 = stub().rejects(error2);
const error = await t.throwsAsync(pipeline([step1, step2, step3], {settleAll: true})(0));
const error = await t.throwsAsync(pipeline([step1, step2, step3], { settleAll: true })(0));
t.deepEqual([...error.errors], [error1, error2]);
t.true(step1.calledWith(0));
@@ -139,31 +139,31 @@ test('Execute all even if a Promise rejects', async (t) => {
t.true(step3.calledWith(0));
});
test('Throw all errors from all steps throwing an AggregateError', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
const error3 = new Error('test error 3');
const error4 = new Error('test error 4');
test("Throw all errors from all steps throwing an AggregateError", async (t) => {
const error1 = new Error("test error 1");
const error2 = new Error("test error 2");
const error3 = new Error("test error 3");
const error4 = new Error("test error 4");
const step1 = stub().rejects(new AggregateError([error1, error2]));
const step2 = stub().rejects(new AggregateError([error3, error4]));
const error = await t.throwsAsync(pipeline([step1, step2], {settleAll: true})(0));
const error = await t.throwsAsync(pipeline([step1, step2], { settleAll: true })(0));
t.deepEqual([...error.errors], [error1, error2, error3, error4]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
});
test('Execute each function in series passing a transformed input even if a step rejects', async (t) => {
const error2 = new Error('test error 2');
const error3 = new Error('test error 3');
test("Execute each function in series passing a transformed input even if a step rejects", async (t) => {
const error2 = new Error("test error 2");
const error3 = new Error("test error 3");
const step1 = stub().resolves(1);
const step2 = stub().rejects(error2);
const step3 = stub().rejects(error3);
const step4 = stub().resolves(4);
const getNextInput = (previousResult, result) => previousResult + result;
const error = await t.throwsAsync(pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput})(0));
const error = await t.throwsAsync(pipeline([step1, step2, step3, step4], { settleAll: true, getNextInput })(0));
t.deepEqual([...error.errors], [error2, error3]);
t.true(step1.calledWith(0));
+163 -157
View File
@@ -1,9 +1,9 @@
import path from 'path';
import test from 'ava';
import {copy, outputFile} from 'fs-extra';
import {stub} from 'sinon';
import {temporaryDirectory} from 'tempy';
import getPlugins from '../../lib/plugins/index.js';
import path from "path";
import test from "ava";
import { copy, outputFile } from "fs-extra";
import { stub } from "sinon";
import { temporaryDirectory } from "tempy";
import getPlugins from "../../lib/plugins/index.js";
// Save the current working directory
const cwd = process.cwd();
@@ -12,32 +12,32 @@ test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.success = stub();
t.context.logger = {log: t.context.log, success: t.context.success, scope: () => t.context.logger};
t.context.logger = { log: t.context.log, success: t.context.success, scope: () => t.context.logger };
});
test('Export default plugins', async (t) => {
const plugins = await getPlugins({cwd, options: {}, logger: t.context.logger}, {});
test("Export default plugins", async (t) => {
const plugins = await getPlugins({ cwd, options: {}, logger: t.context.logger }, {});
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on steps config', async (t) => {
test("Export plugins based on steps config", async (t) => {
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ['./test/fixtures/plugin-noop.cjs', {path: './test/fixtures/plugin-noop.cjs'}],
generateNotes: './test/fixtures/plugin-noop.cjs',
analyzeCommits: {path: './test/fixtures/plugin-noop.cjs'},
verifyConditions: ["./test/fixtures/plugin-noop.cjs", { path: "./test/fixtures/plugin-noop.cjs" }],
generateNotes: "./test/fixtures/plugin-noop.cjs",
analyzeCommits: { path: "./test/fixtures/plugin-noop.cjs" },
verifyRelease: () => {},
},
},
@@ -45,161 +45,161 @@ test('Export plugins based on steps config', async (t) => {
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on "plugins" config (array)', async (t) => {
const plugin1 = {verifyConditions: stub(), publish: stub()};
const plugin2 = {verifyConditions: stub(), verifyRelease: stub()};
const plugin1 = { verifyConditions: stub(), publish: stub() };
const plugin2 = { verifyConditions: stub(), verifyRelease: stub() };
const plugins = await getPlugins(
{cwd, logger: t.context.logger, options: {plugins: [plugin1, [plugin2, {}]], verifyRelease: () => {}}},
{ cwd, logger: t.context.logger, options: { plugins: [plugin1, [plugin2, {}]], verifyRelease: () => {} } },
{}
);
await plugins.verifyConditions({options: {}});
await plugins.verifyConditions({ options: {} });
t.true(plugin1.verifyConditions.calledOnce);
t.true(plugin2.verifyConditions.calledOnce);
await plugins.publish({options: {}});
await plugins.publish({ options: {} });
t.true(plugin1.publish.calledOnce);
await plugins.verifyRelease({options: {}});
await plugins.verifyRelease({ options: {} });
t.true(plugin2.verifyRelease.notCalled);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on "plugins" config (single definition)', async (t) => {
const plugin1 = {verifyConditions: stub(), publish: stub()};
const plugins = await getPlugins({cwd, logger: t.context.logger, options: {plugins: plugin1}}, {});
const plugin1 = { verifyConditions: stub(), publish: stub() };
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: plugin1 } }, {});
await plugins.verifyConditions({options: {}});
await plugins.verifyConditions({ options: {} });
t.true(plugin1.verifyConditions.calledOnce);
await plugins.publish({options: {}});
await plugins.publish({ options: {} });
t.true(plugin1.publish.calledOnce);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Merge global options, "plugins" options and step options', async (t) => {
const plugin1 = [{verifyConditions: stub(), publish: stub()}, {pluginOpt1: 'plugin1'}];
const plugin2 = [{verifyConditions: stub()}, {pluginOpt2: 'plugin2'}];
const plugin3 = [stub(), {pluginOpt3: 'plugin3'}];
const plugin1 = [{ verifyConditions: stub(), publish: stub() }, { pluginOpt1: "plugin1" }];
const plugin2 = [{ verifyConditions: stub() }, { pluginOpt2: "plugin2" }];
const plugin3 = [stub(), { pluginOpt3: "plugin3" }];
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {globalOpt: 'global', plugins: [plugin1, plugin2], verifyRelease: [plugin3]},
options: { globalOpt: "global", plugins: [plugin1, plugin2], verifyRelease: [plugin3] },
},
{}
);
await plugins.verifyConditions({options: {}});
t.deepEqual(plugin1[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
t.deepEqual(plugin2[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt2: 'plugin2'});
await plugins.verifyConditions({ options: {} });
t.deepEqual(plugin1[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
t.deepEqual(plugin2[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt2: "plugin2" });
await plugins.publish({options: {}});
t.deepEqual(plugin1[0].publish.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
await plugins.publish({ options: {} });
t.deepEqual(plugin1[0].publish.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
await plugins.verifyRelease({options: {}});
t.deepEqual(plugin3[0].args[0][0], {globalOpt: 'global', pluginOpt3: 'plugin3'});
await plugins.verifyRelease({ options: {} });
t.deepEqual(plugin3[0].args[0][0], { globalOpt: "global", pluginOpt3: "plugin3" });
});
test('Unknown steps of plugins configured in "plugins" are ignored', async (t) => {
const plugin1 = {verifyConditions: () => {}, unknown: () => {}};
const plugins = await getPlugins({cwd, logger: t.context.logger, options: {plugins: [plugin1]}}, {});
const plugin1 = { verifyConditions: () => {}, unknown: () => {} };
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: [plugin1] } }, {});
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(plugins.unknown, undefined);
});
test('Export plugins loaded from the dependency of a shareable config module', async (t) => {
test("Export plugins loaded from the dependency of a shareable config module", async (t) => {
const cwd = temporaryDirectory();
await copy(
'./test/fixtures/plugin-noop.cjs',
path.resolve(cwd, 'node_modules/shareable-config/node_modules/custom-plugin/index.js')
"./test/fixtures/plugin-noop.cjs",
path.resolve(cwd, "node_modules/shareable-config/node_modules/custom-plugin/index.js")
);
await outputFile(path.resolve(cwd, 'node_modules/shareable-config/index.js'), '');
await outputFile(path.resolve(cwd, "node_modules/shareable-config/index.js"), "");
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ['custom-plugin', {path: 'custom-plugin'}],
generateNotes: 'custom-plugin',
analyzeCommits: {path: 'custom-plugin'},
verifyConditions: ["custom-plugin", { path: "custom-plugin" }],
generateNotes: "custom-plugin",
analyzeCommits: { path: "custom-plugin" },
verifyRelease: () => {},
},
},
{'custom-plugin': 'shareable-config'}
{ "custom-plugin": "shareable-config" }
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins loaded from the dependency of a shareable config file', async (t) => {
test("Export plugins loaded from the dependency of a shareable config file", async (t) => {
const cwd = temporaryDirectory();
await copy('./test/fixtures/plugin-noop.cjs', path.resolve(cwd, 'plugin/plugin-noop.cjs'));
await outputFile(path.resolve(cwd, 'shareable-config.js'), '');
await copy("./test/fixtures/plugin-noop.cjs", path.resolve(cwd, "plugin/plugin-noop.cjs"));
await outputFile(path.resolve(cwd, "shareable-config.js"), "");
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ['./plugin/plugin-noop.cjs', {path: './plugin/plugin-noop.cjs'}],
generateNotes: './plugin/plugin-noop.cjs',
analyzeCommits: {path: './plugin/plugin-noop.cjs'},
verifyConditions: ["./plugin/plugin-noop.cjs", { path: "./plugin/plugin-noop.cjs" }],
generateNotes: "./plugin/plugin-noop.cjs",
analyzeCommits: { path: "./plugin/plugin-noop.cjs" },
verifyRelease: () => {},
},
},
{'./plugin/plugin-noop': './shareable-config.js'}
{ "./plugin/plugin-noop": "./shareable-config.js" }
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Use default when only options are passed for a single plugin', async (t) => {
test("Use default when only options are passed for a single plugin", async (t) => {
const analyzeCommits = {};
const generateNotes = {};
const publish = {};
@@ -211,7 +211,7 @@ test('Use default when only options are passed for a single plugin', async (t) =
cwd,
logger: t.context.logger,
options: {
plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator'],
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
analyzeCommits,
generateNotes,
publish,
@@ -223,96 +223,102 @@ test('Use default when only options are passed for a single plugin', async (t) =
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
// Verify only the plugins defined as an object with no `path` are set to the default value
t.falsy(success.path);
t.falsy(fail.path);
});
test('Merge global options with plugin options', async (t) => {
test("Merge global options with plugin options", async (t) => {
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
globalOpt: 'global',
otherOpt: 'globally-defined',
verifyRelease: {path: './test/fixtures/plugin-result-config', localOpt: 'local', otherOpt: 'locally-defined'},
globalOpt: "global",
otherOpt: "globally-defined",
verifyRelease: { path: "./test/fixtures/plugin-result-config", localOpt: "local", otherOpt: "locally-defined" },
},
},
{}
);
const [result] = await plugins.verifyRelease({options: {}});
const [result] = await plugins.verifyRelease({ options: {} });
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
t.deepEqual(result.pluginConfig, { localOpt: "local", globalOpt: "global", otherOpt: "locally-defined" });
});
test('Throw an error for each invalid plugin configuration', async (t) => {
test("Throw an error for each invalid plugin configuration", async (t) => {
const errors = [
...(await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: {
plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator'],
verifyConditions: 1,
analyzeCommits: [],
verifyRelease: [{}],
generateNotes: [{path: null}],
...(
await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: {
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
verifyConditions: 1,
analyzeCommits: [],
verifyRelease: [{}],
generateNotes: [{ path: null }],
},
},
},
{}
{}
)
)
)).errors,
).errors,
];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINCONF');
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EPLUGINCONF');
t.is(errors[3].name, 'SemanticReleaseError');
t.is(errors[3].code, 'EPLUGINCONF');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINCONF");
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPLUGINCONF");
t.is(errors[3].name, "SemanticReleaseError");
t.is(errors[3].code, "EPLUGINCONF");
});
test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin definition (returns a function)', async (t) => {
const errors = [
...(await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: {plugins: ['./test/fixtures/multi-plugin.cjs', './test/fixtures/plugin-noop.cjs', () => {}]},
},
{}
...(
await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: { plugins: ["./test/fixtures/multi-plugin.cjs", "./test/fixtures/plugin-noop.cjs", () => {}] },
},
{}
)
)
)).errors,
).errors,
];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINSCONF');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINSCONF');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINSCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINSCONF");
});
test('Throw EPLUGINSCONF error for each invalid definition if the "plugins" option', async (t) => {
const errors = [
...(await t.throwsAsync(() =>
getPlugins({cwd, logger: t.context.logger, options: {plugins: [1, {path: 1}, [() => {}, {}, {}]]}}, {})
)).errors,
...(
await t.throwsAsync(() =>
getPlugins({ cwd, logger: t.context.logger, options: { plugins: [1, { path: 1 }, [() => {}, {}, {}]] } }, {})
)
).errors,
];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINSCONF');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINSCONF');
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EPLUGINSCONF');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINSCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINSCONF");
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPLUGINSCONF");
});
+128 -118
View File
@@ -1,214 +1,224 @@
import test from 'ava';
import {loadPlugin, parseConfig, validatePlugin, validateStep} from '../../lib/plugins/utils.js';
import test from "ava";
import { loadPlugin, parseConfig, validatePlugin, validateStep } from "../../lib/plugins/utils.js";
test('validatePlugin', (t) => {
const path = 'plugin-module';
const options = {option1: 'value1', option2: 'value2'};
test("validatePlugin", (t) => {
const path = "plugin-module";
const options = { option1: "value1", option2: "value2" };
t.true(validatePlugin(path), 'String definition');
t.true(validatePlugin({publish: () => {}}), 'Object definition');
t.true(validatePlugin([path]), 'Array definition');
t.true(validatePlugin([path, options]), 'Array definition with options');
t.true(validatePlugin([{publish: () => {}}, options]), 'Array definition with options and path as object');
t.true(validatePlugin({path}), 'Object with path definition');
t.true(validatePlugin({path, ...options}), 'Object with path definition with options');
t.true(validatePlugin(path), "String definition");
t.true(validatePlugin({ publish: () => {} }), "Object definition");
t.true(validatePlugin([path]), "Array definition");
t.true(validatePlugin([path, options]), "Array definition with options");
t.true(validatePlugin([{ publish: () => {} }, options]), "Array definition with options and path as object");
t.true(validatePlugin({ path }), "Object with path definition");
t.true(validatePlugin({ path, ...options }), "Object with path definition with options");
t.true(
validatePlugin({path: {publish: () => {}}, ...options}),
'Object with path definition with options and path as object'
validatePlugin({ path: { publish: () => {} }, ...options }),
"Object with path definition with options and path as object"
);
t.false(validatePlugin(1), 'String definition, wrong path');
t.false(validatePlugin([]), 'Array definition, missing path');
t.false(validatePlugin([path, options, {}]), 'Array definition, additional parameter');
t.false(validatePlugin([1]), 'Array definition, wrong path');
t.false(validatePlugin([path, 1]), 'Array definition, wrong options');
t.false(validatePlugin({path: 1}), 'Object definition, wrong path');
t.false(validatePlugin(1), "String definition, wrong path");
t.false(validatePlugin([]), "Array definition, missing path");
t.false(validatePlugin([path, options, {}]), "Array definition, additional parameter");
t.false(validatePlugin([1]), "Array definition, wrong path");
t.false(validatePlugin([path, 1]), "Array definition, wrong options");
t.false(validatePlugin({ path: 1 }), "Object definition, wrong path");
});
test('validateStep: optional plugin configuration', (t) => {
const type = {multiple: true, required: false};
test("validateStep: optional plugin configuration", (t) => {
const type = { multiple: true, required: false };
// Empty config
t.true(validateStep(type));
t.true(validateStep(type, []));
// Single value definition
t.true(validateStep(type, 'plugin-path.js'));
t.true(validateStep(type, "plugin-path.js"));
t.true(validateStep(type, () => {}));
t.true(validateStep(type, ['plugin-path.js']));
t.true(validateStep(type, ["plugin-path.js"]));
t.true(validateStep(type, [() => {}]));
t.false(validateStep(type, {}));
t.false(validateStep(type, [{}]));
// Array type definition
t.true(validateStep(type, [['plugin-path.js']]));
t.true(validateStep(type, [['plugin-path.js', {options: 'value'}]]));
t.true(validateStep(type, [[() => {}, {options: 'value'}]]));
t.false(validateStep(type, [['plugin-path.js', 1]]));
t.true(validateStep(type, [["plugin-path.js"]]));
t.true(validateStep(type, [["plugin-path.js", { options: "value" }]]));
t.true(validateStep(type, [[() => {}, { options: "value" }]]));
t.false(validateStep(type, [["plugin-path.js", 1]]));
// Object type definition
t.true(validateStep(type, {path: 'plugin-path.js'}));
t.true(validateStep(type, {path: 'plugin-path.js', options: 'value'}));
t.true(validateStep(type, {path: () => {}, options: 'value'}));
t.false(validateStep(type, {path: null}));
t.true(validateStep(type, { path: "plugin-path.js" }));
t.true(validateStep(type, { path: "plugin-path.js", options: "value" }));
t.true(validateStep(type, { path: () => {}, options: "value" }));
t.false(validateStep(type, { path: null }));
// Considered as an Array of 2 definitions and not as one Array definition in case of a muliple plugin type
t.false(validateStep(type, [() => {}, {options: 'value'}]));
t.false(validateStep(type, ['plugin-path.js', {options: 'value'}]));
t.false(validateStep(type, [() => {}, { options: "value" }]));
t.false(validateStep(type, ["plugin-path.js", { options: "value" }]));
// Multiple definitions
t.true(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', 1],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", 1],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
{},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: null},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: null },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
});
test('validateStep: required plugin configuration', (t) => {
const type = {required: true};
test("validateStep: required plugin configuration", (t) => {
const type = { required: true };
// Empty config
t.false(validateStep(type));
t.false(validateStep(type, []));
// Single value definition
t.true(validateStep(type, 'plugin-path.js'));
t.true(validateStep(type, "plugin-path.js"));
t.true(validateStep(type, () => {}));
t.true(validateStep(type, ['plugin-path.js']));
t.true(validateStep(type, ["plugin-path.js"]));
t.true(validateStep(type, [() => {}]));
t.false(validateStep(type, {}));
t.false(validateStep(type, [{}]));
// Array type definition
t.true(validateStep(type, [['plugin-path.js']]));
t.true(validateStep(type, [['plugin-path.js', {options: 'value'}]]));
t.true(validateStep(type, [[() => {}, {options: 'value'}]]));
t.false(validateStep(type, [['plugin-path.js', 1]]));
t.true(validateStep(type, [["plugin-path.js"]]));
t.true(validateStep(type, [["plugin-path.js", { options: "value" }]]));
t.true(validateStep(type, [[() => {}, { options: "value" }]]));
t.false(validateStep(type, [["plugin-path.js", 1]]));
// Object type definition
t.true(validateStep(type, {path: 'plugin-path.js'}));
t.true(validateStep(type, {path: 'plugin-path.js', options: 'value'}));
t.true(validateStep(type, {path: () => {}, options: 'value'}));
t.false(validateStep(type, {path: null}));
t.true(validateStep(type, { path: "plugin-path.js" }));
t.true(validateStep(type, { path: "plugin-path.js", options: "value" }));
t.true(validateStep(type, { path: () => {}, options: "value" }));
t.false(validateStep(type, { path: null }));
// Considered as an Array of 2 definitions and not as one Array definition in the case of a muliple plugin type
t.false(validateStep(type, [() => {}, {options: 'value'}]));
t.false(validateStep(type, ['plugin-path.js', {options: 'value'}]));
t.false(validateStep(type, [() => {}, { options: "value" }]));
t.false(validateStep(type, ["plugin-path.js", { options: "value" }]));
// Multiple definitions
t.true(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', 1],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", 1],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
{},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: null},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: null },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
});
test('loadPlugin', async (t) => {
test("loadPlugin", async (t) => {
const cwd = process.cwd();
const func = () => {};
t.is((await import('../fixtures/plugin-noop.cjs')).default, await loadPlugin({cwd: './test/fixtures'}, './plugin-noop.cjs', {}), 'From cwd');
t.is(
(await import('../fixtures/plugin-noop.cjs')).default,
await loadPlugin({cwd}, './plugin-noop.cjs', {'./plugin-noop.cjs': './test/fixtures'}),
'From a shareable config context'
(await import("../fixtures/plugin-noop.cjs")).default,
await loadPlugin({ cwd: "./test/fixtures" }, "./plugin-noop.cjs", {}),
"From cwd"
);
t.is(func, await loadPlugin({cwd}, func, {}), 'Defined as a function');
t.is(
(await import("../fixtures/plugin-noop.cjs")).default,
await loadPlugin({ cwd }, "./plugin-noop.cjs", { "./plugin-noop.cjs": "./test/fixtures" }),
"From a shareable config context"
);
const { ...namedExports } = await import("../fixtures/plugin-esm-named-exports.js");
const plugin = await loadPlugin({ cwd }, "./plugin-esm-named-exports.js", {
"./plugin-esm-named-exports.js": "./test/fixtures",
});
t.deepEqual(namedExports, plugin, "ESM with named exports");
t.is(func, await loadPlugin({ cwd }, func, {}), "Defined as a function");
});
test('parseConfig', (t) => {
const path = 'plugin-module';
const options = {option1: 'value1', option2: 'value2'};
test("parseConfig", (t) => {
const path = "plugin-module";
const options = { option1: "value1", option2: "value2" };
t.deepEqual(parseConfig(path), [path, {}], 'String definition');
t.deepEqual(parseConfig({path}), [path, {}], 'Object definition');
t.deepEqual(parseConfig({path, ...options}), [path, options], 'Object definition with options');
t.deepEqual(parseConfig([path]), [path, {}], 'Array definition');
t.deepEqual(parseConfig([path, options]), [path, options], 'Array definition with options');
t.deepEqual(parseConfig(path), [path, {}], "String definition");
t.deepEqual(parseConfig({ path }), [path, {}], "Object definition");
t.deepEqual(parseConfig({ path, ...options }), [path, options], "Object definition with options");
t.deepEqual(parseConfig([path]), [path, {}], "Array definition");
t.deepEqual(parseConfig([path, options]), [path, options], "Array definition with options");
});