first commit
This commit is contained in:
27
node_modules/release-it/test/args.js
generated
vendored
Normal file
27
node_modules/release-it/test/args.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseCliArguments } from '../lib/args.js';
|
||||
|
||||
test('should parse boolean arguments', () => {
|
||||
const args = [
|
||||
'--dry-run=false',
|
||||
'--ci',
|
||||
'--github=false',
|
||||
'--no-npm',
|
||||
'--git.addUntrackedFiles=true',
|
||||
'--git.commit=false',
|
||||
'--no-git.tag',
|
||||
'--git.commitMessage=test'
|
||||
];
|
||||
|
||||
const result = parseCliArguments(args);
|
||||
|
||||
assert.equal(result['dry-run'], false);
|
||||
assert.equal(result.ci, true);
|
||||
assert.equal(result.github, false);
|
||||
assert.equal(result.npm, false);
|
||||
assert.equal(result.git.addUntrackedFiles, true);
|
||||
assert.equal(result.git.commit, false);
|
||||
assert.equal(result.git.tag, false);
|
||||
assert.equal(result.git.commitMessage, 'test');
|
||||
});
|
||||
21
node_modules/release-it/test/cli.js
generated
vendored
Normal file
21
node_modules/release-it/test/cli.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import mockStdIo from 'mock-stdio';
|
||||
import { version, help } from '../lib/cli.js';
|
||||
import { readJSON } from '../lib/util.js';
|
||||
|
||||
const pkg = readJSON(new URL('../package.json', import.meta.url));
|
||||
|
||||
test('should print version', () => {
|
||||
mockStdIo.start();
|
||||
version();
|
||||
const { stdout } = mockStdIo.end();
|
||||
assert.equal(stdout, `v${pkg.version}\n`);
|
||||
});
|
||||
|
||||
test('should print help', () => {
|
||||
mockStdIo.start();
|
||||
help();
|
||||
const { stdout } = mockStdIo.end();
|
||||
assert.match(stdout, new RegExp(`Release It!.+${pkg.version}`));
|
||||
});
|
||||
333
node_modules/release-it/test/config.js
generated
vendored
Normal file
333
node_modules/release-it/test/config.js
generated
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
import test, { describe, before, after, afterEach } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { isCI } from 'ci-info';
|
||||
import Config from '../lib/config.js';
|
||||
import { readJSON } from '../lib/util.js';
|
||||
import { mockFetch } from './util/mock.js';
|
||||
import { createTarBlobByRawContents } from './util/fetch.js';
|
||||
|
||||
const defaultConfig = readJSON(new URL('../config/release-it.json', import.meta.url));
|
||||
const projectConfig = readJSON(new URL('../.release-it.json', import.meta.url));
|
||||
|
||||
const localConfig = { github: { release: true } };
|
||||
|
||||
describe('config', async () => {
|
||||
test("should read this project's own configuration", async () => {
|
||||
const config = new Config();
|
||||
await config.init();
|
||||
assert.deepEqual(config.constructorConfig, {});
|
||||
assert.deepEqual(config.localConfig, projectConfig);
|
||||
assert.deepEqual(config.defaultConfig, defaultConfig);
|
||||
});
|
||||
|
||||
test('should contain default values', async () => {
|
||||
const config = new Config({ configDir: './test/stub/config/default' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.constructorConfig, { configDir: './test/stub/config/default' });
|
||||
assert.deepEqual(config.localConfig, localConfig);
|
||||
assert.deepEqual(config.defaultConfig, defaultConfig);
|
||||
});
|
||||
|
||||
test('should merge provided options', async () => {
|
||||
const config = new Config({
|
||||
configDir: './test/stub/config/merge',
|
||||
increment: '1.0.0',
|
||||
verbose: true,
|
||||
github: {
|
||||
release: true
|
||||
}
|
||||
});
|
||||
await config.init();
|
||||
const { options } = config;
|
||||
assert.equal(config.isVerbose, true);
|
||||
assert.equal(config.isDryRun, false);
|
||||
assert.equal(options.increment, '1.0.0');
|
||||
assert.equal(options.git.push, false);
|
||||
assert.equal(options.github.release, true);
|
||||
});
|
||||
|
||||
test('should set CI mode', async () => {
|
||||
const config = new Config({ ci: true });
|
||||
await config.init();
|
||||
assert.equal(config.isCI, true);
|
||||
});
|
||||
|
||||
test('should detect CI mode', async () => {
|
||||
const config = new Config();
|
||||
await config.init();
|
||||
assert.equal(config.options.ci, isCI);
|
||||
assert.equal(config.isCI, isCI);
|
||||
});
|
||||
|
||||
test('should override --no-npm.publish', async () => {
|
||||
const config = new Config({ npm: { publish: false } });
|
||||
await config.init();
|
||||
assert.equal(config.options.npm.publish, false);
|
||||
});
|
||||
|
||||
test('should read YAML config', async () => {
|
||||
const config = new Config({ configDir: './test/stub/config/yaml' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.foo, { bar: 1 });
|
||||
});
|
||||
|
||||
test('should read YML config', async () => {
|
||||
const config = new Config({ configDir: './test/stub/config/yml' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.foo, { bar: 1 });
|
||||
});
|
||||
|
||||
test('should read TOML config', async () => {
|
||||
const config = new Config({ configDir: './test/stub/config/toml' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.foo, { bar: 1 });
|
||||
});
|
||||
|
||||
test('should throw if provided config file is invalid (cosmiconfig exception)', async () => {
|
||||
await assert.rejects(async () => {
|
||||
const config = new Config({ config: './test/stub/config/invalid-config-txt' });
|
||||
await config.init();
|
||||
}, /Invalid configuration file at/);
|
||||
});
|
||||
|
||||
test('should throw if provided config file is invalid (no object)', async () => {
|
||||
await assert.rejects(async () => {
|
||||
const config = new Config({ config: './test/stub/config/invalid-config-rc' });
|
||||
await config.init();
|
||||
}, /Invalid configuration file at/);
|
||||
});
|
||||
|
||||
test('should not set default increment (for CI mode)', async () => {
|
||||
const config = new Config({ ci: true });
|
||||
await config.init();
|
||||
assert.equal(config.options.version.increment, undefined);
|
||||
});
|
||||
|
||||
test('should not set default increment (for interactive mode)', async () => {
|
||||
const config = new Config({ ci: false });
|
||||
await config.init();
|
||||
assert.equal(config.options.version.increment, undefined);
|
||||
});
|
||||
|
||||
test('should expand pre-release shortcut', async () => {
|
||||
const config = new Config({ increment: 'major', preRelease: 'beta' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.version, {
|
||||
increment: 'major',
|
||||
isPreRelease: true,
|
||||
preReleaseBase: undefined,
|
||||
preReleaseId: 'beta'
|
||||
});
|
||||
});
|
||||
|
||||
test('should expand pre-release shortcut (preRelease boolean)', async () => {
|
||||
const config = new Config({ ci: true, preRelease: true });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.version, {
|
||||
increment: undefined,
|
||||
isPreRelease: true,
|
||||
preReleaseBase: undefined,
|
||||
preReleaseId: undefined
|
||||
});
|
||||
});
|
||||
|
||||
test('should expand pre-release shortcut (without increment)', async () => {
|
||||
const config = new Config({ ci: false, preRelease: 'alpha' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.version, {
|
||||
increment: undefined,
|
||||
isPreRelease: true,
|
||||
preReleaseBase: undefined,
|
||||
preReleaseId: 'alpha'
|
||||
});
|
||||
});
|
||||
|
||||
test('should expand pre-release shortcut (including increment and npm.tag)', async () => {
|
||||
const config = new Config({ increment: 'minor', preRelease: 'rc' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.version, {
|
||||
increment: 'minor',
|
||||
isPreRelease: true,
|
||||
preReleaseBase: undefined,
|
||||
preReleaseId: 'rc'
|
||||
});
|
||||
});
|
||||
|
||||
test('should use pre-release base', async () => {
|
||||
const config = new Config({ increment: 'minor', preRelease: 'next', preReleaseBase: '1' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.version, {
|
||||
increment: 'minor',
|
||||
isPreRelease: true,
|
||||
preReleaseBase: '1',
|
||||
preReleaseId: 'next'
|
||||
});
|
||||
});
|
||||
|
||||
test('should expand pre-release shortcut (snapshot)', async () => {
|
||||
const config = new Config({ snapshot: 'feat' });
|
||||
await config.init();
|
||||
assert.deepEqual(config.options.version, {
|
||||
increment: 'prerelease',
|
||||
isPreRelease: true,
|
||||
preReleaseBase: undefined,
|
||||
preReleaseId: 'feat'
|
||||
});
|
||||
assert.equal(config.options.git.tagMatch, '0.0.0-feat.[0-9]*');
|
||||
assert.equal(config.options.git.getLatestTagFromAllRefs, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetch extended configuration', () => {
|
||||
const [mocker, server] = mockFetch('https://api.github.com');
|
||||
|
||||
before(() => {
|
||||
mocker.mockGlobal();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mocker.clearAll();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
mocker.unmockGlobal();
|
||||
});
|
||||
|
||||
test('should fetch extended configuration with default file and default branch', async () => {
|
||||
const extendedConfiguration = {
|
||||
git: {
|
||||
commitMessage: 'Released version ${version}'
|
||||
}
|
||||
};
|
||||
|
||||
server.head('/repos/release-it/release-it-configuration/tarball/main', {
|
||||
status: 200,
|
||||
headers: {}
|
||||
});
|
||||
|
||||
server.get('/repos/release-it/release-it-configuration/tarball/main', {
|
||||
status: 200,
|
||||
body: await new Response(
|
||||
createTarBlobByRawContents({
|
||||
'.release-it.json': JSON.stringify(extendedConfiguration)
|
||||
})
|
||||
).arrayBuffer()
|
||||
});
|
||||
|
||||
const config = new Config({
|
||||
extends: 'github:release-it/release-it-configuration'
|
||||
});
|
||||
await config.init();
|
||||
|
||||
assert(mocker.allRoutesCalled());
|
||||
|
||||
assert.equal(config.options.git.commitMessage, extendedConfiguration.git.commitMessage);
|
||||
});
|
||||
|
||||
test('should fetch extended configuration with default file and specific tag', async () => {
|
||||
const extendedConfiguration = {
|
||||
git: {
|
||||
commitMessage: 'Released version ${version}'
|
||||
}
|
||||
};
|
||||
|
||||
server.head('/repos/release-it/release-it-configuration/tarball/1.0.0', {
|
||||
status: 200,
|
||||
headers: {}
|
||||
});
|
||||
|
||||
server.get('/repos/release-it/release-it-configuration/tarball/1.0.0', {
|
||||
status: 200,
|
||||
body: await new Response(
|
||||
createTarBlobByRawContents({
|
||||
'.release-it.json': JSON.stringify(extendedConfiguration)
|
||||
})
|
||||
).arrayBuffer()
|
||||
});
|
||||
|
||||
const config = new Config({
|
||||
extends: 'github:release-it/release-it-configuration#1.0.0'
|
||||
});
|
||||
await config.init();
|
||||
|
||||
assert(mocker.allRoutesCalled());
|
||||
|
||||
assert.equal(config.options.git.commitMessage, extendedConfiguration.git.commitMessage);
|
||||
});
|
||||
|
||||
test('should fetch extended configuration with sub dir and specific tag', async () => {
|
||||
const extendedConfiguration = {
|
||||
git: {
|
||||
commitMessage: 'Released version ${version}'
|
||||
}
|
||||
};
|
||||
|
||||
const extendedSubConfiguration = {
|
||||
git: {
|
||||
commitMessage: 'Released pkg version ${version}'
|
||||
}
|
||||
};
|
||||
|
||||
server.head('/repos/release-it/release-it-configuration/tarball/1.0.0', {
|
||||
status: 200,
|
||||
headers: {}
|
||||
});
|
||||
|
||||
server.get('/repos/release-it/release-it-configuration/tarball/1.0.0', {
|
||||
status: 200,
|
||||
body: await new Response(
|
||||
createTarBlobByRawContents({
|
||||
'.release-it.json': JSON.stringify(extendedConfiguration),
|
||||
'sub/.release-it.json': JSON.stringify(extendedSubConfiguration)
|
||||
})
|
||||
).arrayBuffer()
|
||||
});
|
||||
|
||||
const config = new Config({
|
||||
extends: 'github:release-it/release-it-configuration/sub#1.0.0'
|
||||
});
|
||||
await config.init();
|
||||
|
||||
assert(mocker.allRoutesCalled());
|
||||
|
||||
assert.equal(config.options.git.commitMessage, extendedSubConfiguration.git.commitMessage);
|
||||
});
|
||||
|
||||
test('should fetch extended configuration with custom file and default branch', async () => {
|
||||
const extendedConfiguration = {
|
||||
git: {
|
||||
commitMessage: 'Released version ${version}'
|
||||
}
|
||||
};
|
||||
|
||||
const extendedSubConfiguration = {
|
||||
git: {
|
||||
commitMessage: 'Released pkg version ${version}'
|
||||
}
|
||||
};
|
||||
|
||||
server.head('/repos/release-it/release-it-configuration/tarball/main', {
|
||||
status: 200,
|
||||
headers: {}
|
||||
});
|
||||
|
||||
server.get('/repos/release-it/release-it-configuration/tarball/main', {
|
||||
status: 200,
|
||||
body: await new Response(
|
||||
createTarBlobByRawContents({
|
||||
'.release-it.json': JSON.stringify(extendedConfiguration),
|
||||
'sub/.release-it.json': JSON.stringify(extendedSubConfiguration)
|
||||
})
|
||||
).arrayBuffer()
|
||||
});
|
||||
|
||||
const config = new Config({
|
||||
extends: 'github:release-it/release-it-configuration/sub'
|
||||
});
|
||||
await config.init();
|
||||
|
||||
assert(mocker.allRoutesCalled());
|
||||
|
||||
assert.equal(config.options.git.commitMessage, extendedSubConfiguration.git.commitMessage);
|
||||
});
|
||||
});
|
||||
255
node_modules/release-it/test/git.init.js
generated
vendored
Normal file
255
node_modules/release-it/test/git.init.js
generated
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
import childProcess from 'node:child_process';
|
||||
import test, { beforeEach, describe } from 'node:test';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import Shell from '../lib/shell.js';
|
||||
import Git from '../lib/plugin/git/Git.js';
|
||||
import { execOpts, readJSON } from '../lib/util.js';
|
||||
import sh from './util/sh.js';
|
||||
import { factory } from './util/index.js';
|
||||
import { mkTmpDir, gitAdd } from './util/helpers.js';
|
||||
|
||||
describe('git.init', () => {
|
||||
const { git } = readJSON(new URL('../config/release-it.json', import.meta.url));
|
||||
|
||||
let bare;
|
||||
let target;
|
||||
beforeEach(() => {
|
||||
bare = mkTmpDir();
|
||||
target = mkTmpDir();
|
||||
process.chdir(bare);
|
||||
sh.exec(`git init --bare .`, execOpts);
|
||||
sh.exec(`git clone ${bare} ${target}`, execOpts);
|
||||
process.chdir(target);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
});
|
||||
|
||||
test('should throw if on wrong branch', async () => {
|
||||
const options = { git: { requireBranch: 'dev' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
childProcess.execSync('git remote remove origin', execOpts);
|
||||
await assert.rejects(gitClient.init(), /Must be on branch dev/);
|
||||
});
|
||||
|
||||
test('should throw if on negated branch', async () => {
|
||||
const options = { git: { requireBranch: '!main' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
sh.exec('git checkout -b main', execOpts);
|
||||
await assert.rejects(gitClient.init(), /Must be on branch !main/);
|
||||
});
|
||||
|
||||
test('should not throw if required branch matches', async () => {
|
||||
const options = { git: { requireBranch: 'ma?*' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
await assert.doesNotReject(gitClient.init());
|
||||
});
|
||||
|
||||
test('should not throw if one of required branch matches', async () => {
|
||||
const options = { git: { requireBranch: ['release/*', 'hotfix/*'] } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
childProcess.execSync('git checkout -b release/v1', execOpts);
|
||||
await assert.doesNotReject(gitClient.init());
|
||||
});
|
||||
|
||||
test('should throw if there is no remote Git url', async () => {
|
||||
const gitClient = await factory(Git, { options: { git } });
|
||||
childProcess.execSync('git remote remove origin', execOpts);
|
||||
await assert.rejects(gitClient.init(), /Could not get remote Git url/);
|
||||
});
|
||||
|
||||
test('should throw if working dir is not clean', async () => {
|
||||
const gitClient = await factory(Git, { options: { git } });
|
||||
childProcess.execSync('rm file', execOpts);
|
||||
await assert.rejects(gitClient.init(), /Working dir must be clean/);
|
||||
});
|
||||
|
||||
test('should throw if no upstream is configured', async () => {
|
||||
const gitClient = await factory(Git, { options: { git } });
|
||||
childProcess.execSync('git checkout -b foo', execOpts);
|
||||
await assert.rejects(gitClient.init(), /No upstream configured for current branch/);
|
||||
});
|
||||
|
||||
test('should throw if there are no commits', async () => {
|
||||
const options = { git: { requireCommits: true } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
await assert.rejects(gitClient.init(), /There are no commits since the latest tag/);
|
||||
});
|
||||
|
||||
test('should not throw if there are commits', async () => {
|
||||
const options = { git: { requireCommits: true } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
await assert.doesNotReject(gitClient.init(), 'There are no commits since the latest tag');
|
||||
});
|
||||
|
||||
test('should fail (exit code 1) if there are no commits', async () => {
|
||||
const options = { git: { requireCommits: true } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
await assert.rejects(gitClient.init(), { code: 1 });
|
||||
});
|
||||
|
||||
test('should not fail (exit code 0) if there are no commits', async () => {
|
||||
const options = { git: { requireCommits: true, requireCommitsFail: false } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
await assert.rejects(gitClient.init(), { code: 0 });
|
||||
});
|
||||
|
||||
test('should throw if there are no commits in specified path', async () => {
|
||||
const options = { git: { requireCommits: true, commitsPath: 'dir' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
mkdirSync('dir', { recursive: true });
|
||||
sh.exec('git tag 1.0.0', execOpts);
|
||||
await assert.rejects(gitClient.init(), { message: /^There are no commits since the latest tag/ });
|
||||
});
|
||||
|
||||
test('should not throw if there are commits in specified path', async () => {
|
||||
const options = { git: { requireCommits: true, commitsPath: 'dir' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
sh.exec('git tag 1.0.0', execOpts);
|
||||
gitAdd('line', 'dir/file', 'Add file');
|
||||
await assert.doesNotReject(gitClient.init());
|
||||
});
|
||||
|
||||
test('should not throw if there are no tags', async () => {
|
||||
const options = { git: { requireCommits: true } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
await assert.doesNotReject(gitClient.init());
|
||||
});
|
||||
|
||||
test('should not throw if origin remote is renamed', async () => {
|
||||
childProcess.execSync('git remote rename origin upstream', execOpts);
|
||||
const gitClient = await factory(Git);
|
||||
await assert.doesNotReject(gitClient.init());
|
||||
});
|
||||
|
||||
test('should detect and include version prefix ("v")', async () => {
|
||||
const gitClient = await factory(Git, { options: { git } });
|
||||
childProcess.execSync('git tag v1.0.0', execOpts);
|
||||
await gitClient.init();
|
||||
await gitClient.bump('1.0.1');
|
||||
assert.equal(gitClient.config.getContext('tagName'), 'v1.0.1');
|
||||
});
|
||||
|
||||
test('should detect and exclude version prefix', async () => {
|
||||
const gitClient = await factory(Git, { options: { git } });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
await gitClient.init();
|
||||
await gitClient.bump('1.0.1');
|
||||
assert.equal(gitClient.config.getContext('tagName'), '1.0.1');
|
||||
});
|
||||
|
||||
test('should detect and exclude version prefix (configured)', async () => {
|
||||
const gitClient = await factory(Git, { options: { git: { tagName: 'v${version}' } } });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
await gitClient.init();
|
||||
await gitClient.bump('1.0.1');
|
||||
assert.equal(gitClient.config.getContext('tagName'), 'v1.0.1');
|
||||
});
|
||||
|
||||
test('should honor custom tagName configuration', async () => {
|
||||
const gitClient = await factory(Git, { options: { git: { tagName: 'TAGNAME-${repo.project}-v${version}' } } });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
await gitClient.init();
|
||||
await gitClient.bump('1.0.1');
|
||||
const { project } = gitClient.getContext('repo');
|
||||
assert.equal(gitClient.config.getContext('tagName'), `TAGNAME-${project}-v1.0.1`);
|
||||
});
|
||||
|
||||
test('should get the latest tag after fetch', async () => {
|
||||
const shell = await factory(Shell);
|
||||
const gitClient = await factory(Git, { container: { shell } });
|
||||
const other = mkTmpDir();
|
||||
childProcess.execSync('git push', execOpts);
|
||||
childProcess.execSync(`git clone ${bare} ${other}`, execOpts);
|
||||
|
||||
process.chdir(other);
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
childProcess.execSync('git push --tags', execOpts);
|
||||
process.chdir(target);
|
||||
await gitClient.init();
|
||||
assert.equal(gitClient.config.getContext('latestTag'), '1.0.0');
|
||||
});
|
||||
|
||||
test('should get the latest custom tag after fetch when tagName is configured', async () => {
|
||||
const shell = await factory(Shell);
|
||||
const gitClient = await factory(Git, {
|
||||
options: { git: { tagName: 'TAGNAME-v${version}' } },
|
||||
container: { shell }
|
||||
});
|
||||
const other = mkTmpDir();
|
||||
childProcess.execSync('git push', execOpts);
|
||||
childProcess.execSync(`git clone ${bare} ${other}`, execOpts);
|
||||
process.chdir(other);
|
||||
childProcess.execSync('git tag TAGNAME-OTHER-v2.0.0', execOpts);
|
||||
childProcess.execSync('git tag TAGNAME-v1.0.0', execOpts);
|
||||
childProcess.execSync('git tag TAGNAME-OTHER-v2.0.2', execOpts);
|
||||
childProcess.execSync('git push --tags', execOpts);
|
||||
process.chdir(target);
|
||||
await gitClient.init();
|
||||
assert.equal(gitClient.config.getContext('latestTag'), 'TAGNAME-v1.0.0');
|
||||
});
|
||||
|
||||
test('should get the latest tag based on tagMatch', async () => {
|
||||
const shell = await factory(Shell);
|
||||
const gitClient = await factory(Git, {
|
||||
options: { git: { tagMatch: '[0-9][0-9]\\.[0-1][0-9]\\.[0-9]*' } },
|
||||
container: { shell }
|
||||
});
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
childProcess.execSync('git tag 21.04.3', execOpts);
|
||||
childProcess.execSync('git tag 1.0.1', execOpts);
|
||||
childProcess.execSync('git push --tags', execOpts);
|
||||
await gitClient.init();
|
||||
assert.equal(gitClient.config.getContext('latestTag'), '21.04.3');
|
||||
});
|
||||
|
||||
test('should get the latest tag based on tagExclude', async () => {
|
||||
const shell = await factory(Shell);
|
||||
const gitClient = await factory(Git, {
|
||||
options: { git: { tagExclude: '*[-]*' } },
|
||||
container: { shell }
|
||||
});
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
childProcess.execSync('git commit --allow-empty -m "commit 1"', execOpts);
|
||||
childProcess.execSync('git tag 1.0.1-rc.0', execOpts);
|
||||
childProcess.execSync('git tag 1.0.1', execOpts);
|
||||
childProcess.execSync('git commit --allow-empty -m "commit 2"', execOpts);
|
||||
childProcess.execSync('git tag 1.1.0-rc.0', execOpts);
|
||||
childProcess.execSync('git push --tags', execOpts);
|
||||
await gitClient.init();
|
||||
assert.equal(gitClient.config.getContext('latestTag'), '1.0.1');
|
||||
});
|
||||
|
||||
test('should generate correct changelog', async () => {
|
||||
const gitClient = await factory(Git, { options: { git } });
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
await gitClient.init();
|
||||
const changelog = await gitClient.getChangelog();
|
||||
assert.match(changelog, /\* Add file \(\w{7}\)\n\* Add file \(\w{7}\)/);
|
||||
});
|
||||
|
||||
test('should get the full changelog since latest major tag', async () => {
|
||||
const shell = await factory(Shell);
|
||||
const gitClient = await factory(Git, {
|
||||
options: { git: { tagMatch: '[0-9]\\.[0-9]\\.[0-9]', changelog: git.changelog } },
|
||||
container: { shell }
|
||||
});
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync('git tag 2.0.0-rc.0', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync('git tag 2.0.0-rc.1', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
await gitClient.init();
|
||||
assert.equal(gitClient.config.getContext('latestTag'), '1.0.0');
|
||||
const changelog = await gitClient.getChangelog();
|
||||
assert.match(changelog, /\* Add file \(\w{7}\)\n\* Add file \(\w{7}\)\n\* Add file \(\w{7}\)/);
|
||||
});
|
||||
});
|
||||
409
node_modules/release-it/test/git.js
generated
vendored
Normal file
409
node_modules/release-it/test/git.js
generated
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
import test, { beforeEach, describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { EOL } from 'node:os';
|
||||
import childProcess from 'node:child_process';
|
||||
import { appendFileSync } from 'node:fs';
|
||||
import Git from '../lib/plugin/git/Git.js';
|
||||
import { execOpts, touch } from '../lib/util.js';
|
||||
import sh from './util/sh.js';
|
||||
import { factory } from './util/index.js';
|
||||
import { mkTmpDir, readFile, gitAdd } from './util/helpers.js';
|
||||
|
||||
describe('git', () => {
|
||||
beforeEach(() => {
|
||||
const tmp = mkTmpDir();
|
||||
process.chdir(tmp);
|
||||
});
|
||||
|
||||
test('should return whether repo has upstream branch', async () => {
|
||||
const gitClient = await factory(Git);
|
||||
childProcess.execSync('git init', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
assert.equal(await gitClient.hasUpstreamBranch(), false);
|
||||
});
|
||||
|
||||
test('should return branch name', async () => {
|
||||
const gitClient = await factory(Git);
|
||||
childProcess.execSync('git init', execOpts);
|
||||
assert.equal(await gitClient.getBranchName(), null);
|
||||
childProcess.execSync('git checkout -b feat', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
assert.equal(await gitClient.getBranchName(), 'feat');
|
||||
});
|
||||
|
||||
test('should return whether tag exists and if working dir is clean', async () => {
|
||||
const gitClient = await factory(Git);
|
||||
childProcess.execSync('git init', execOpts);
|
||||
assert.equal(await gitClient.tagExists('1.0.0'), false);
|
||||
touch('file');
|
||||
assert.equal(await gitClient.isWorkingDirClean(), false);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
assert(await gitClient.tagExists('1.0.0'));
|
||||
assert(await gitClient.isWorkingDirClean());
|
||||
});
|
||||
|
||||
test('should throw if tag exists', async () => {
|
||||
const gitClient = await factory(Git);
|
||||
childProcess.execSync('git init', execOpts);
|
||||
touch('file');
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync('git tag 0.0.2', execOpts);
|
||||
gitClient.config.setContext({ latestTag: '0.0.1', tagName: '0.0.2' });
|
||||
await assert.rejects(gitClient.tag({ name: '0.0.2' }), /fatal: tag '0\.0\.2' already exists/);
|
||||
});
|
||||
|
||||
test('should only warn if tag exists intentionally', async t => {
|
||||
const gitClient = await factory(Git);
|
||||
const warn = t.mock.method(gitClient.log, 'warn');
|
||||
childProcess.execSync('git init', execOpts);
|
||||
touch('file');
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
gitClient.config.setContext({ latestTag: '1.0.0', tagName: '1.0.0' });
|
||||
await assert.doesNotReject(gitClient.tag());
|
||||
assert.equal(warn.mock.callCount(), 1);
|
||||
assert.equal(warn.mock.calls[0].arguments[0], 'Tag "1.0.0" already exists');
|
||||
});
|
||||
|
||||
test('should return the remote url', async () => {
|
||||
childProcess.execSync(`git init`, execOpts);
|
||||
{
|
||||
const options = { git: { pushRepo: 'origin' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
assert.equal(await gitClient.getRemoteUrl(), null);
|
||||
childProcess.execSync(`git remote add origin foo`, execOpts);
|
||||
assert.equal(await gitClient.getRemoteUrl(), 'foo');
|
||||
}
|
||||
{
|
||||
const options = { git: { pushRepo: 'another' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
assert.equal(await gitClient.getRemoteUrl(), null);
|
||||
childProcess.execSync(`git remote add another bar`, execOpts);
|
||||
assert.equal(await gitClient.getRemoteUrl(), 'bar');
|
||||
}
|
||||
{
|
||||
const options = { git: { pushRepo: 'git://github.com/webpro/release-it.git' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
assert.equal(await gitClient.getRemoteUrl(), 'git://github.com/webpro/release-it.git');
|
||||
}
|
||||
});
|
||||
|
||||
test('should return the non-origin remote', async () => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync('git remote rename origin upstream', execOpts);
|
||||
const gitClient = await factory(Git);
|
||||
assert.equal(await gitClient.getRemoteUrl(), bare);
|
||||
});
|
||||
|
||||
test('should stage, commit, tag and push', async () => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
const version = '1.2.3';
|
||||
gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
|
||||
{
|
||||
const gitClient = await factory(Git);
|
||||
childProcess.execSync(`git tag ${version}`, execOpts);
|
||||
assert.equal(await gitClient.getLatestTagName(), version);
|
||||
}
|
||||
{
|
||||
const gitClient = await factory(Git);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync('npm --no-git-tag-version version patch', execOpts);
|
||||
await gitClient.stage('package.json');
|
||||
await gitClient.commit({ message: `Release v1.2.4` });
|
||||
await gitClient.tag({ name: 'v1.2.4', annotation: 'Release v1.2.4' });
|
||||
assert.equal(await gitClient.getLatestTagName(), 'v1.2.4');
|
||||
await gitClient.push();
|
||||
const stdout = childProcess.execSync('git status -uno', { encoding: 'utf-8' });
|
||||
assert.match(stdout, /nothing to commit/);
|
||||
}
|
||||
});
|
||||
|
||||
test('should commit, tag and push with extra args', async t => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
const options = { git: { commitArgs: '-S', tagArgs: ['-T', 'foo'], pushArgs: ['-U', 'bar', '-V'] } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
const stub = t.mock.method(gitClient.shell, 'exec', () => Promise.resolve());
|
||||
await gitClient.stage('package.json');
|
||||
await gitClient.commit({ message: `Release v1.2.4` });
|
||||
await gitClient.tag({ name: 'v1.2.4', annotation: 'Release v1.2.4' });
|
||||
await gitClient.push();
|
||||
assert(stub.mock.calls[1].arguments[0].includes('-S'));
|
||||
assert.equal(stub.mock.calls[2].arguments[0][5], '-T');
|
||||
assert.equal(stub.mock.calls[2].arguments[0][6], 'foo');
|
||||
assert(stub.mock.calls.at(-1).arguments[0].join(' ').includes('-U bar -V'));
|
||||
});
|
||||
|
||||
test('should amend commit without message if not provided', async t => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
const options = { git: { commitArgs: ['--amend', '--no-edit', '--no-verify'] } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
const exec = t.mock.method(gitClient.shell, 'exec', () => Promise.resolve());
|
||||
await gitClient.stage('package.json');
|
||||
await gitClient.commit();
|
||||
assert.deepEqual(exec.mock.calls[1].arguments[0], ['git', 'commit', '--amend', '--no-edit', '--no-verify']);
|
||||
});
|
||||
|
||||
test('should commit and tag with quoted characters', async () => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
const gitClient = await factory(Git, {
|
||||
options: { git: { commitMessage: 'Release ${version}', tagAnnotation: 'Release ${version}\n\n${changelog}' } }
|
||||
});
|
||||
touch('file');
|
||||
const changelog = `- Foo's${EOL}- "$bar"${EOL}- '$baz'${EOL}- foo`;
|
||||
gitClient.config.setContext({ version: '1.0.0', changelog });
|
||||
|
||||
await gitClient.stage('file');
|
||||
await gitClient.commit();
|
||||
await gitClient.tag({ name: '1.0.0' });
|
||||
await gitClient.push();
|
||||
{
|
||||
const stdout = childProcess.execSync('git log -1 --format=%s', { encoding: 'utf-8' });
|
||||
assert.equal(stdout.trim(), 'Release 1.0.0');
|
||||
}
|
||||
{
|
||||
const stdout = childProcess.execSync('git tag -n99', { encoding: 'utf-8' });
|
||||
assert.equal(
|
||||
stdout.trim(),
|
||||
`1.0.0 Release 1.0.0\n \n - Foo's\n - "$bar"\n - '$baz'\n - foo`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('should push to origin', async t => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
const gitClient = await factory(Git);
|
||||
const spy = t.mock.method(gitClient.shell, 'exec');
|
||||
await gitClient.push();
|
||||
assert.deepEqual(spy.mock.calls.at(-1).arguments[0], ['git', 'push']);
|
||||
const stdout = childProcess.execSync('git ls-tree -r HEAD --name-only', {
|
||||
cwd: bare,
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
assert.equal(stdout.trim(), 'file');
|
||||
});
|
||||
|
||||
test('should push to tracked upstream branch', async t => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
childProcess.execSync(`git remote rename origin upstream`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
const gitClient = await factory(Git);
|
||||
const spy = t.mock.method(gitClient.shell, 'exec');
|
||||
await gitClient.push();
|
||||
assert.deepEqual(spy.mock.calls.at(-1).arguments[0], ['git', 'push']);
|
||||
const stdout = childProcess.execSync('git ls-tree -r HEAD --name-only', {
|
||||
cwd: bare,
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
assert.equal(stdout.trim(), 'file');
|
||||
});
|
||||
|
||||
test('should push to repo url', async t => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
const options = { git: { pushRepo: 'https://host/repo.git' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
const spy = t.mock.method(gitClient.shell, 'exec');
|
||||
try {
|
||||
await gitClient.push();
|
||||
} catch (err) {
|
||||
assert.deepEqual(spy.mock.calls.at(-1).arguments[0], ['git', 'push', 'https://host/repo.git']);
|
||||
}
|
||||
});
|
||||
|
||||
test('should push to remote name (not "origin")', async t => {
|
||||
const bare = mkTmpDir();
|
||||
childProcess.execSync(`git init --bare ${bare}`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} .`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
childProcess.execSync(
|
||||
`git remote add upstream ${childProcess.execSync('git config --get remote.origin.url', {
|
||||
encoding: 'utf-8'
|
||||
})}`,
|
||||
execOpts
|
||||
);
|
||||
const options = { git: { pushRepo: 'upstream' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
const spy = t.mock.method(gitClient.shell, 'exec');
|
||||
await gitClient.push();
|
||||
assert.deepEqual(spy.mock.calls.at(-1).arguments[0], ['git', 'push', 'upstream']);
|
||||
const stdout = childProcess.execSync('git ls-tree -r HEAD --name-only', {
|
||||
cwd: bare,
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
assert.equal(stdout.trim(), 'file');
|
||||
|
||||
{
|
||||
childProcess.execSync(`git checkout -b foo`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
await gitClient.push();
|
||||
assert.deepEqual(spy.mock.calls.at(-1).arguments[0], ['git', 'push', '--set-upstream', 'upstream', 'foo']);
|
||||
assert.match(
|
||||
await spy.mock.calls.at(-1).result,
|
||||
/branch .?foo.? set up to track (remote branch .?foo.? from .?upstream.?|.?upstream\/foo.?)/i
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('should return repo status', async () => {
|
||||
const gitClient = await factory(Git);
|
||||
childProcess.execSync('git init', execOpts);
|
||||
gitAdd('line', 'file1', 'Add file');
|
||||
|
||||
appendFileSync('file1', 'line');
|
||||
|
||||
appendFileSync('file2', 'line');
|
||||
childProcess.execSync('git add file2', execOpts);
|
||||
assert.equal(await gitClient.status(), ' M file1\nA file2');
|
||||
});
|
||||
|
||||
test('should reset files', async t => {
|
||||
const gitClient = await factory(Git);
|
||||
childProcess.execSync('git init', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
|
||||
appendFileSync('file', 'line');
|
||||
assert.match(await readFile('file'), /^line\s*line\s*$/);
|
||||
await gitClient.reset('file');
|
||||
assert.match(await readFile('file'), /^line\s*$/);
|
||||
const warn = t.mock.method(gitClient.log, 'warn');
|
||||
await gitClient.reset(['file2, file3']);
|
||||
assert.match(warn.mock.calls[0].arguments[0], /Could not reset file2, file3/);
|
||||
});
|
||||
|
||||
test('should roll back when cancelled', async t => {
|
||||
childProcess.execSync('git init', execOpts);
|
||||
childProcess.execSync(`git remote add origin file://foo`, execOpts);
|
||||
const version = '1.2.3';
|
||||
gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
|
||||
const options = { git: { requireCleanWorkingDir: true, commit: true, tag: true, tagName: 'v${version}' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
const exec = t.mock.method(gitClient.shell, 'execFormattedCommand');
|
||||
childProcess.execSync(`git tag ${version}`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
|
||||
await gitClient.init();
|
||||
|
||||
childProcess.execSync('npm --no-git-tag-version version patch', execOpts);
|
||||
|
||||
gitClient.bump('1.2.4');
|
||||
await gitClient.beforeRelease();
|
||||
await gitClient.stage('package.json');
|
||||
await gitClient.commit({ message: 'Add this' });
|
||||
await gitClient.tag();
|
||||
await gitClient.rollbackOnce();
|
||||
|
||||
assert.equal(exec.mock.calls[11].arguments[0], 'git tag --delete v1.2.4');
|
||||
assert.equal(exec.mock.calls[12].arguments[0], 'git reset --hard HEAD~1');
|
||||
});
|
||||
|
||||
// To get this test to pass, I had to switch between spawnsync and execsync somehow
|
||||
test('should remove remote tag when push to branch failed', async t => {
|
||||
childProcess.execSync('git init', execOpts);
|
||||
childProcess.execSync(`git remote add origin file://foo`, execOpts);
|
||||
sh.exec(`git remote update`, execOpts);
|
||||
const version = '1.2.3';
|
||||
gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
|
||||
const options = { git: { requireCleanWorkingDir: true, commit: true, tag: true, tagName: 'v${version}' } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
const exec = t.mock.method(gitClient.shell, 'execFormattedCommand');
|
||||
sh.exec(`git push`, execOpts);
|
||||
sh.exec(`git checkout HEAD~1`, execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
|
||||
await gitClient.init();
|
||||
|
||||
childProcess.execSync('npm --no-git-tag-version version patch', execOpts);
|
||||
|
||||
gitClient.bump('1.2.4');
|
||||
await gitClient.beforeRelease();
|
||||
await gitClient.stage('package.json');
|
||||
await gitClient.commit({ message: 'Add this' });
|
||||
await gitClient.tag();
|
||||
try {
|
||||
await gitClient.push();
|
||||
} catch (e) {
|
||||
// push would fail with an error since HEAD is behind origin
|
||||
}
|
||||
assert.equal(exec.mock.calls[15].arguments[0], 'git push origin --delete v1.2.4');
|
||||
});
|
||||
|
||||
test('should not touch existing history when rolling back', async t => {
|
||||
childProcess.execSync('git init', execOpts);
|
||||
const version = '1.2.3';
|
||||
gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
|
||||
const options = { git: { requireCleanWorkingDir: true, commit: true, tag: true } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
childProcess.execSync(`git tag ${version}`, execOpts);
|
||||
|
||||
const exec = t.mock.method(gitClient.shell, 'execFormattedCommand');
|
||||
gitClient.config.setContext({ version: '1.2.4' });
|
||||
await gitClient.beforeRelease();
|
||||
await gitClient.commit();
|
||||
await gitClient.rollbackOnce();
|
||||
|
||||
assert.equal(exec.mock.calls[3].arguments[0], 'git reset --hard HEAD');
|
||||
});
|
||||
|
||||
test.skip('should not roll back with risky config', async () => {
|
||||
childProcess.execSync('git init', execOpts);
|
||||
const options = { git: { requireCleanWorkingDir: false, commit: true, tag: true } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
await gitClient.beforeRelease();
|
||||
assert.equal('rollbackOnce' in gitClient, false);
|
||||
});
|
||||
|
||||
test('should return latest tag from default branch (not parent commit)', async () => {
|
||||
childProcess.execSync('git init', execOpts);
|
||||
|
||||
{
|
||||
const options = { git: { getLatestTagFromAllRefs: true } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
gitAdd('main', 'file', 'Add file in main');
|
||||
const defaultBranchName = await gitClient.getBranchName();
|
||||
const developBranchName = 'develop';
|
||||
const featureBranchPrefix = 'feature';
|
||||
await gitClient.tag({ name: '1.0.0' });
|
||||
childProcess.execSync(`git branch ${developBranchName} ${defaultBranchName}`, execOpts);
|
||||
childProcess.execSync(`git checkout -b ${featureBranchPrefix}/first ${developBranchName}`, execOpts);
|
||||
gitAdd('feature/1', 'file', 'Update file in feature branch (1)');
|
||||
childProcess.execSync(`git checkout ${developBranchName}`, execOpts);
|
||||
childProcess.execSync(`git merge --no-ff ${featureBranchPrefix}/first`, execOpts);
|
||||
await gitClient.tag({ name: '1.1.0-rc.1' });
|
||||
childProcess.execSync(`git checkout ${defaultBranchName}`, execOpts);
|
||||
childProcess.execSync(`git merge --no-ff ${developBranchName}`, execOpts);
|
||||
await gitClient.tag({ name: '1.1.0' });
|
||||
childProcess.execSync(`git checkout -b ${featureBranchPrefix}/second ${developBranchName}`, execOpts);
|
||||
gitAdd('feature/2', 'file', 'Update file again, in feature branch (2)');
|
||||
childProcess.execSync(`git checkout ${developBranchName}`, execOpts);
|
||||
childProcess.execSync(`git merge --no-ff ${featureBranchPrefix}/second`, execOpts);
|
||||
assert.equal(await gitClient.getLatestTagName(), '1.1.0');
|
||||
}
|
||||
|
||||
{
|
||||
const options = { git: { getLatestTagFromAllRefs: false } };
|
||||
const gitClient = await factory(Git, { options });
|
||||
assert.equal(await gitClient.getLatestTagName(), '1.1.0-rc.1');
|
||||
}
|
||||
});
|
||||
});
|
||||
630
node_modules/release-it/test/github.js
generated
vendored
Normal file
630
node_modules/release-it/test/github.js
generated
vendored
Normal file
@@ -0,0 +1,630 @@
|
||||
import test, { describe, before, after, afterEach } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { RequestError } from '@octokit/request-error';
|
||||
import GitHub from '../lib/plugin/github/GitHub.js';
|
||||
import { getSearchQueries } from '../lib/plugin/github/util.js';
|
||||
import { factory, runTasks } from './util/index.js';
|
||||
import {
|
||||
interceptAuthentication,
|
||||
interceptCollaborator,
|
||||
interceptListReleases,
|
||||
interceptCreate,
|
||||
interceptUpdate,
|
||||
interceptAsset
|
||||
} from './stub/github.js';
|
||||
import { mockFetch } from './util/mock.js';
|
||||
|
||||
describe('github', () => {
|
||||
const tokenRef = 'GITHUB_TOKEN';
|
||||
const pushRepo = 'git://github.com/user/repo';
|
||||
const host = 'github.com';
|
||||
const git = { changelog: '' };
|
||||
const requestErrorOptions = { request: { url: '', headers: {} }, response: { headers: {} } };
|
||||
|
||||
const [mocker, api, assets, example, custom] = mockFetch([
|
||||
'https://api.github.com',
|
||||
'https://uploads.github.com',
|
||||
'https://github.example.org/api/v3',
|
||||
'https://custom.example.org/api/v3'
|
||||
]);
|
||||
|
||||
before(() => {
|
||||
mocker.mockGlobal();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mocker.clearAll();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
mocker.unmockGlobal();
|
||||
});
|
||||
|
||||
test('should check token and perform checks', async () => {
|
||||
const tokenRef = 'MY_GITHUB_TOKEN';
|
||||
const options = { github: { release: true, tokenRef, pushRepo } };
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
await assert.doesNotReject(github.init());
|
||||
});
|
||||
|
||||
test('should check token and warn', async () => {
|
||||
const tokenRef = 'MY_GITHUB_TOKEN';
|
||||
const options = { github: { release: true, tokenRef, pushRepo } };
|
||||
const github = await factory(GitHub, { options });
|
||||
delete process.env[tokenRef];
|
||||
|
||||
await assert.doesNotReject(github.init());
|
||||
|
||||
assert.equal(
|
||||
github.log.warn.mock.calls[0].arguments[0],
|
||||
'Environment variable "MY_GITHUB_TOKEN" is required for automated GitHub Releases.'
|
||||
);
|
||||
assert.equal(github.log.warn.mock.calls[1].arguments[0], 'Falling back to web-based GitHub Release.');
|
||||
});
|
||||
|
||||
test('should release and upload assets', async t => {
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
releaseNotes: 'echo Custom notes',
|
||||
assets: 'test/resources/file-v${version}.txt'
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptCreate(api, { body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes' } });
|
||||
interceptAsset(assets, { body: '*' });
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
|
||||
});
|
||||
|
||||
test('should create a pre-release and draft release notes', async t => {
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
preRelease: true,
|
||||
draft: true
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptCreate(api, { body: { tag_name: '2.0.2', name: 'Release 2.0.2', prerelease: true, draft: true } });
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
|
||||
});
|
||||
|
||||
test('should create auto-generated release notes', async t => {
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
autoGenerate: true
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptCreate(api, {
|
||||
body: { tag_name: '2.0.2', name: 'Release 2.0.2', generate_release_notes: true, body: '' }
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
|
||||
});
|
||||
|
||||
test('should update release and upload assets', async t => {
|
||||
const asset = 'file1';
|
||||
const options = {
|
||||
increment: false,
|
||||
git,
|
||||
github: {
|
||||
update: true,
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
releaseNotes: 'echo Custom notes',
|
||||
assets: `test/resources/${asset}`
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
if (args[0] === 'git rev-list 2.0.1 --tags --max-count=1') return Promise.resolve('a123456');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptListReleases(api, { tag_name: '2.0.1' });
|
||||
interceptUpdate(api, { body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
|
||||
interceptAsset(assets, { body: asset });
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
|
||||
});
|
||||
|
||||
test('should create custom release notes using releaseNotes function', async t => {
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
releaseNotes(context) {
|
||||
return `Custom notes for tag ${context.tagName}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptCreate(api, {
|
||||
body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes for tag 2.0.2' }
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
|
||||
});
|
||||
|
||||
test('should create new release for unreleased tag', async t => {
|
||||
const options = {
|
||||
increment: false,
|
||||
git,
|
||||
github: {
|
||||
update: true,
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
releaseNotes: 'echo Custom notes'
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
if (args[0] === 'git rev-list 2.0.1 --tags --max-count=1') return Promise.resolve('b123456');
|
||||
if (args[0] === 'git describe --tags --abbrev=0 "b123456^"') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptListReleases(api, { tag_name: '2.0.0' });
|
||||
interceptCreate(api, { body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
|
||||
});
|
||||
|
||||
test('should release to enterprise host', async t => {
|
||||
const options = { git, github: { tokenRef, pushRepo: 'git://github.example.org/user/repo' } };
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git remote get-url origin') return Promise.resolve(`https://github.example.org/user/repo`);
|
||||
if (args[0] === 'git config --get remote.origin.url')
|
||||
return Promise.resolve('https://github.example.org/user/repo');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('1.0.0');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(example);
|
||||
interceptCollaborator(example);
|
||||
interceptCreate(example, {
|
||||
api: 'https://github.example.org/api/v3',
|
||||
host: 'github.example.org',
|
||||
body: { tag_name: '1.0.1' }
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.example.org/user/repo/releases/tag/1.0.1');
|
||||
});
|
||||
|
||||
test('should release to alternative host and proxy', async t => {
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
tokenRef,
|
||||
pushRepo: `git://custom.example.org/user/repo`,
|
||||
host: 'custom.example.org',
|
||||
proxy: 'http://proxy:8080'
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('1.0.0');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(custom);
|
||||
interceptCollaborator(custom);
|
||||
interceptCreate(custom, {
|
||||
api: 'https://custom.example.org/api/v3',
|
||||
host: 'custom.example.org',
|
||||
body: { tag_name: '1.0.1' }
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://custom.example.org/user/repo/releases/tag/1.0.1');
|
||||
assert.equal(github.options.proxy, 'http://proxy:8080');
|
||||
});
|
||||
|
||||
test('should release to git.pushRepo', async t => {
|
||||
const options = { git: { pushRepo: 'upstream', changelog: '' }, github: { tokenRef, skipChecks: true } };
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('1.0.0');
|
||||
if (args[0] === 'git remote get-url upstream') return Promise.resolve('https://custom.example.org/user/repo');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptCreate(custom, {
|
||||
api: 'https://custom.example.org/api/v3',
|
||||
host: 'custom.example.org',
|
||||
body: { tag_name: '1.0.1' }
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://custom.example.org/user/repo/releases/tag/1.0.1');
|
||||
});
|
||||
|
||||
const testSkipOnActions = process.env.GITHUB_ACTIONS ? test.skip : test;
|
||||
|
||||
testSkipOnActions('should throw for unauthenticated user', async t => {
|
||||
const options = { github: { tokenRef, pushRepo, host } };
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const getAuthenticated = t.mock.method(github.client.users, 'getAuthenticated', () => {
|
||||
throw new RequestError('Bad credentials', 401, requestErrorOptions);
|
||||
});
|
||||
|
||||
await assert.rejects(runTasks(github), {
|
||||
message: /Could not authenticate with GitHub using environment variable "GITHUB_TOKEN"/
|
||||
});
|
||||
|
||||
assert.equal(getAuthenticated.mock.callCount(), 1);
|
||||
});
|
||||
|
||||
testSkipOnActions('should throw for non-collaborator', async t => {
|
||||
const options = { github: { tokenRef, pushRepo, host } };
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
t.mock.method(github.client.repos, 'checkCollaborator', () => {
|
||||
throw new RequestError('HttpError', 401, requestErrorOptions);
|
||||
});
|
||||
|
||||
interceptAuthentication(api, { username: 'john' });
|
||||
|
||||
await assert.rejects(runTasks(github), /User john is not a collaborator for user\/repo/);
|
||||
});
|
||||
|
||||
test('should skip authentication and collaborator checks when running on GitHub Actions', async t => {
|
||||
const { GITHUB_ACTIONS, GITHUB_ACTOR } = process.env;
|
||||
if (!GITHUB_ACTIONS) {
|
||||
process.env.GITHUB_ACTIONS = 1;
|
||||
process.env.GITHUB_ACTOR = 'webpro';
|
||||
}
|
||||
|
||||
const options = { github: { tokenRef } };
|
||||
const github = await factory(GitHub, { options });
|
||||
const authStub = t.mock.method(github, 'isAuthenticated');
|
||||
const collaboratorStub = t.mock.method(github, 'isCollaborator');
|
||||
|
||||
await assert.doesNotReject(github.init());
|
||||
|
||||
assert.equal(authStub.mock.callCount(), 0);
|
||||
assert.equal(collaboratorStub.mock.callCount(), 0);
|
||||
assert.equal(github.getContext('username'), process.env.GITHUB_ACTOR);
|
||||
|
||||
if (!GITHUB_ACTIONS) {
|
||||
process.env.GITHUB_ACTIONS = GITHUB_ACTIONS || '';
|
||||
process.env.GITHUB_ACTOR = GITHUB_ACTOR || '';
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle octokit client error (without retries)', async t => {
|
||||
const github = await factory(GitHub, { options: { github: { tokenRef, pushRepo, host } } });
|
||||
const createRelease = t.mock.method(github.client.repos, 'createRelease', () => {
|
||||
throw new RequestError('Not found', 404, requestErrorOptions);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
|
||||
await assert.rejects(runTasks(github), /404 \(Not found\)/);
|
||||
|
||||
assert.equal(createRelease.mock.callCount(), 1);
|
||||
});
|
||||
|
||||
test('should handle octokit client error (with retries)', async t => {
|
||||
const options = { github: { tokenRef, pushRepo, host, retryMinTimeout: 0 } };
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const createRelease = t.mock.method(github.client.repos, 'createRelease', () => {
|
||||
throw new RequestError('Request failed', 500, requestErrorOptions);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
|
||||
await assert.rejects(runTasks(github), /500 \(Request failed\)/);
|
||||
|
||||
assert.equal(createRelease.mock.callCount(), 3);
|
||||
});
|
||||
|
||||
test('should not call octokit client in dry run', async t => {
|
||||
const options = {
|
||||
'dry-run': true,
|
||||
git,
|
||||
github: { tokenRef, pushRepo, releaseName: 'R ${version}', assets: ['*'] }
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const get = t.mock.getter(github, 'client');
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('v1.0.0');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
assert.equal(get.mock.callCount(), 0);
|
||||
assert.equal(github.log.exec.mock.calls[1].arguments[0], 'octokit repos.createRelease "R 1.0.1" (v1.0.1)');
|
||||
assert.equal(github.log.exec.mock.calls.at(-1).arguments[0], 'octokit repos.uploadReleaseAssets');
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/v1.0.1');
|
||||
});
|
||||
|
||||
test('should generate GitHub web release url', async t => {
|
||||
const options = {
|
||||
github: {
|
||||
pushRepo,
|
||||
release: true,
|
||||
web: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
releaseNotes: 'echo Custom notes'
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(
|
||||
releaseUrl,
|
||||
'https://github.com/user/repo/releases/new?tag=2.0.2&title=Release+2.0.2&body=Custom+notes&prerelease=false'
|
||||
);
|
||||
});
|
||||
|
||||
test('should generate GitHub web release url for enterprise host', async t => {
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
pushRepo: 'git@custom.example.org:user/repo',
|
||||
release: true,
|
||||
web: true,
|
||||
host: 'custom.example.org',
|
||||
releaseName: 'The Launch',
|
||||
releaseNotes: 'echo It happened'
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(
|
||||
releaseUrl,
|
||||
'https://custom.example.org/user/repo/releases/new?tag=2.0.2&title=The+Launch&body=It+happened&prerelease=false'
|
||||
);
|
||||
});
|
||||
|
||||
test.skip('should truncate long body', async t => {
|
||||
const releaseNotes = 'a'.repeat(125001);
|
||||
const body = 'a'.repeat(124000) + '...';
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
releaseNotes: 'echo ' + releaseNotes
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git log --pretty=format:"* %s (%h)" ${from}...${to}') return Promise.resolve('');
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptCreate(api, { body: { tag_name: '2.0.2', name: 'Release 2.0.2', body } });
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
|
||||
});
|
||||
|
||||
test('should generate search queries correctly', () => {
|
||||
const generateCommit = () => Math.random().toString(36).substring(2, 9);
|
||||
const base = 'repo:owner/repo+type:pr+is:merged';
|
||||
const commits = Array.from({ length: 5 }, generateCommit);
|
||||
const separator = '+';
|
||||
|
||||
const result = getSearchQueries(base, commits, separator);
|
||||
|
||||
// Test case 1: Check if all commits are included in the search queries
|
||||
const allCommitsIncluded = commits.every(commit => result.some(query => query.includes(commit)));
|
||||
assert(allCommitsIncluded, 'All commits should be included in the search queries');
|
||||
|
||||
assert.equal(
|
||||
commits.every(commit => result.some(query => query.includes(commit))),
|
||||
true
|
||||
);
|
||||
|
||||
// Test case 2: Check if the function respects the 256 character limit
|
||||
const manyCommits = Array.from({ length: 100 }, generateCommit);
|
||||
const longResult = getSearchQueries(base, manyCommits, separator);
|
||||
assert(longResult.length > 1, 'Many commits should be split into multiple queries');
|
||||
assert(
|
||||
longResult.every(query => encodeURIComponent(query).length <= 256),
|
||||
'Each query should not exceed 256 characters after encoding'
|
||||
);
|
||||
});
|
||||
|
||||
test('should create auto-generated discussion', async t => {
|
||||
const options = {
|
||||
git,
|
||||
github: {
|
||||
pushRepo,
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${tagName}',
|
||||
autoGenerate: false,
|
||||
discussionCategoryName: 'Announcement'
|
||||
}
|
||||
};
|
||||
const github = await factory(GitHub, { options });
|
||||
const original = github.shell.exec.bind(github.shell);
|
||||
t.mock.method(github.shell, 'exec', (...args) => {
|
||||
if (args[0] === 'git describe --tags --match=* --abbrev=0') return Promise.resolve('2.0.1');
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
interceptAuthentication(api);
|
||||
interceptCollaborator(api);
|
||||
interceptCreate(api, {
|
||||
body: {
|
||||
tag_name: '2.0.2',
|
||||
name: 'Release 2.0.2',
|
||||
generate_release_notes: false,
|
||||
body: null,
|
||||
discussion_category_name: 'Announcement'
|
||||
}
|
||||
});
|
||||
|
||||
await runTasks(github);
|
||||
|
||||
const { isReleased, releaseUrl, discussionUrl } = github.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
|
||||
assert.equal(discussionUrl, 'https://github.com/user/repo/discussions/1');
|
||||
});
|
||||
});
|
||||
455
node_modules/release-it/test/gitlab.js
generated
vendored
Normal file
455
node_modules/release-it/test/gitlab.js
generated
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
import fs from 'node:fs';
|
||||
import test, { before, after, afterEach, beforeEach, describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { Agent } from 'undici';
|
||||
import Git from '../lib/plugin/git/Git.js';
|
||||
import GitLab from '../lib/plugin/gitlab/GitLab.js';
|
||||
import { GitlabTestServer } from './util/https-server/server.js';
|
||||
import { factory, runTasks } from './util/index.js';
|
||||
import {
|
||||
interceptUser,
|
||||
interceptCollaborator,
|
||||
interceptPublish,
|
||||
interceptAsset,
|
||||
interceptAssetGeneric,
|
||||
interceptMilestones,
|
||||
interceptMembers
|
||||
} from './stub/gitlab.js';
|
||||
import { mockFetch } from './util/mock.js';
|
||||
|
||||
describe('GitLab', () => {
|
||||
const tokenHeader = 'Private-Token';
|
||||
const tokenRef = 'GITLAB_TOKEN';
|
||||
const certificateAuthorityFileRef = 'CI_SERVER_TLS_CA_FILE';
|
||||
|
||||
const [mocker, api, example, local] = mockFetch([
|
||||
'https://gitlab.com/api/v4',
|
||||
'https://gitlab.example.org/api/v4',
|
||||
'https://localhost:3000/api/v4'
|
||||
]);
|
||||
|
||||
before(() => {
|
||||
mocker.mockGlobal();
|
||||
});
|
||||
|
||||
let originalEnv;
|
||||
beforeEach(() => {
|
||||
originalEnv = process.env;
|
||||
process.env = { ...originalEnv };
|
||||
|
||||
process.env[tokenRef] = '123';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalEnv !== undefined) process.env = originalEnv;
|
||||
mocker.clearAll();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
mocker.unmockGlobal();
|
||||
});
|
||||
|
||||
test('should validate token', async () => {
|
||||
const tokenRef = 'MY_GITLAB_TOKEN';
|
||||
const pushRepo = 'https://gitlab.com/user/repo';
|
||||
const options = { gitlab: { release: true, tokenRef, tokenHeader, pushRepo } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
delete process.env[tokenRef];
|
||||
|
||||
await assert.rejects(gitlab.init(), /Environment variable "MY_GITLAB_TOKEN" is required for GitLab releases/);
|
||||
|
||||
process.env[tokenRef] = '123';
|
||||
|
||||
interceptUser(api, { headers: { 'private-token': '123' } });
|
||||
interceptCollaborator(api, { headers: { 'private-token': '123' } });
|
||||
await assert.doesNotReject(gitlab.init());
|
||||
});
|
||||
|
||||
test('should support CI Job token header', async () => {
|
||||
const tokenRef = 'CI_JOB_TOKEN';
|
||||
const tokenHeader = 'Job-Token';
|
||||
process.env[tokenRef] = 'j0b-t0k3n';
|
||||
const pushRepo = 'https://gitlab.com/user/repo';
|
||||
const options = { git: { pushRepo }, gitlab: { release: true, tokenRef, tokenHeader } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
|
||||
interceptPublish(api, { headers: { 'job-token': '1' } });
|
||||
|
||||
await assert.doesNotReject(gitlab.init());
|
||||
|
||||
delete process.env[tokenRef];
|
||||
});
|
||||
|
||||
test('should upload assets and release', async t => {
|
||||
const pushRepo = 'https://gitlab.com/user/repo';
|
||||
const options = {
|
||||
git: { pushRepo },
|
||||
gitlab: {
|
||||
tokenRef,
|
||||
release: true,
|
||||
releaseName: 'Release ${version}',
|
||||
releaseNotes: 'echo Custom notes',
|
||||
assets: 'test/resources/file-v${version}.txt',
|
||||
milestones: ['${version}', '${latestVersion} UAT']
|
||||
}
|
||||
};
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
t.mock.method(gitlab, 'getLatestVersion', () => Promise.resolve('2.0.0'));
|
||||
|
||||
const git = await factory(Git);
|
||||
const ref = (await git.getBranchName()) ?? 'HEAD';
|
||||
|
||||
interceptUser(api);
|
||||
interceptCollaborator(api);
|
||||
interceptMilestones(api, { query: { title: '2.0.1' }, milestones: [{ id: 17, iid: 3, title: '2.0.1' }] });
|
||||
interceptMilestones(api, { query: { title: '2.0.0 UAT' }, milestones: [{ id: 42, iid: 4, title: '2.0.0 UAT' }] });
|
||||
interceptAsset(api);
|
||||
interceptPublish(api, {
|
||||
body: {
|
||||
name: 'Release 2.0.1',
|
||||
ref,
|
||||
tag_name: '2.0.1',
|
||||
tag_message: 'Release 2.0.1',
|
||||
description: 'Custom notes',
|
||||
assets: {
|
||||
links: [
|
||||
{ name: 'file-v2.0.1.txt', url: `${pushRepo}/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file-v2.0.1.txt` }
|
||||
]
|
||||
},
|
||||
milestones: ['2.0.1', '2.0.0 UAT']
|
||||
}
|
||||
});
|
||||
|
||||
await runTasks(gitlab);
|
||||
|
||||
assert.equal(gitlab.assets[0].url, `${pushRepo}/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file-v2.0.1.txt`);
|
||||
const { isReleased, releaseUrl } = gitlab.getContext();
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, `${pushRepo}/-/releases/2.0.1`);
|
||||
});
|
||||
|
||||
test('should upload assets with ID-based URLs', async t => {
|
||||
const host = 'https://gitlab.com';
|
||||
const pushRepo = `${host}/user/repo`;
|
||||
const options = {
|
||||
git: { pushRepo },
|
||||
gitlab: {
|
||||
tokenRef,
|
||||
release: true,
|
||||
assets: 'test/resources/file-v${version}.txt',
|
||||
useIdsForUrls: true
|
||||
}
|
||||
};
|
||||
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
t.mock.method(gitlab, 'getLatestVersion', () => Promise.resolve('2.0.0'));
|
||||
|
||||
interceptUser(api);
|
||||
interceptCollaborator(api);
|
||||
interceptAsset(api);
|
||||
interceptPublish(api);
|
||||
|
||||
await runTasks(gitlab);
|
||||
|
||||
assert.equal(
|
||||
gitlab.assets[0].url,
|
||||
`${host}/-/project/1234/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file-v2.0.1.txt`
|
||||
);
|
||||
});
|
||||
|
||||
test('should upload assets to generic repo', async t => {
|
||||
const host = 'https://gitlab.com';
|
||||
const pushRepo = `${host}/user/repo`;
|
||||
const options = {
|
||||
git: { pushRepo },
|
||||
gitlab: {
|
||||
tokenRef,
|
||||
release: true,
|
||||
assets: 'test/resources/file-v${version}.txt',
|
||||
useGenericPackageRepositoryForAssets: true,
|
||||
genericPackageRepositoryName: 'release-it'
|
||||
}
|
||||
};
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
t.mock.method(gitlab, 'getLatestVersion', () => Promise.resolve('2.0.0'));
|
||||
|
||||
interceptUser(api);
|
||||
interceptCollaborator(api);
|
||||
interceptAssetGeneric(api);
|
||||
interceptPublish(api);
|
||||
|
||||
await runTasks(gitlab);
|
||||
|
||||
assert.equal(
|
||||
gitlab.assets[0].url,
|
||||
`${host}/api/v4/projects/user%2Frepo/packages/generic/release-it/2.0.1/file-v2.0.1.txt`
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw when release milestone is missing', async t => {
|
||||
const pushRepo = 'https://gitlab.com/user/repo';
|
||||
const options = {
|
||||
git: { pushRepo },
|
||||
gitlab: {
|
||||
tokenRef,
|
||||
release: true,
|
||||
milestones: ['${version}', '${latestVersion} UAT']
|
||||
}
|
||||
};
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
t.mock.method(gitlab, 'getLatestVersion', () => Promise.resolve('2.0.0'));
|
||||
|
||||
interceptUser(api);
|
||||
interceptCollaborator(api);
|
||||
interceptMilestones(api, { query: { title: '2.0.1' }, milestones: [{ id: 17, iid: 3, title: '2.0.1' }] });
|
||||
interceptMilestones(api, { query: { title: '2.0.0 UAT' }, milestones: [] });
|
||||
|
||||
await assert.rejects(
|
||||
runTasks(gitlab),
|
||||
/Missing one or more milestones in GitLab. Creating a GitLab release will fail./
|
||||
);
|
||||
});
|
||||
|
||||
test('should release to self-managed host', async t => {
|
||||
const host = 'https://gitlab.example.org';
|
||||
const options = {
|
||||
git: { pushRepo: `${host}/user/repo` },
|
||||
gitlab: { releaseName: 'Release ${version}', releaseNotes: 'echo readme', tokenRef }
|
||||
};
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
t.mock.method(gitlab, 'getLatestVersion', () => Promise.resolve('1.0.0'));
|
||||
|
||||
interceptUser(example);
|
||||
interceptCollaborator(example);
|
||||
interceptPublish(example);
|
||||
|
||||
await runTasks(gitlab);
|
||||
|
||||
const { origin, baseUrl } = gitlab.getContext();
|
||||
assert.equal(origin, host);
|
||||
assert.equal(baseUrl, `${host}/api/v4`);
|
||||
});
|
||||
|
||||
test('should release to sub-grouped repo', async () => {
|
||||
const options = { gitlab: { tokenRef }, git: { pushRepo: 'git@gitlab.com:group/sub-group/repo.git' } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
|
||||
interceptUser(api, { owner: 'sub-group' });
|
||||
interceptCollaborator(api, { owner: 'sub-group', group: 'group' });
|
||||
interceptPublish(api, { owner: 'group', project: 'sub-group%2Frepo' });
|
||||
|
||||
await runTasks(gitlab);
|
||||
|
||||
const { isReleased, releaseUrl } = gitlab.getContext();
|
||||
assert(isReleased);
|
||||
assert.match(releaseUrl, /https:\/\/gitlab.com\/group\/sub-group(\/|%2F)repo\/-\/releases\//);
|
||||
});
|
||||
|
||||
test('should throw for unauthenticated user', async () => {
|
||||
const host = 'https://gitlab.com';
|
||||
const pushRepo = `${host}/user/repo`;
|
||||
const options = { gitlab: { tokenRef, pushRepo, host } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
|
||||
api.get('/user', { status: 401 });
|
||||
|
||||
await assert.rejects(
|
||||
runTasks(gitlab),
|
||||
/Could not authenticate with GitLab using environment variable "GITLAB_TOKEN"/
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw for non-collaborator', async () => {
|
||||
const host = 'https://gitlab.com';
|
||||
const pushRepo = `${host}/john/repo`;
|
||||
const options = { gitlab: { tokenRef, pushRepo, host } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
|
||||
interceptMembers(api, { owner: 'emma' });
|
||||
interceptUser(api, { owner: 'john' });
|
||||
|
||||
await assert.rejects(runTasks(gitlab), /User john is not a collaborator for john\/repo/);
|
||||
});
|
||||
|
||||
test('should throw for insufficient access level', async () => {
|
||||
const host = 'https://gitlab.com';
|
||||
const pushRepo = `${host}/john/repo`;
|
||||
const options = { gitlab: { tokenRef, pushRepo, host } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
|
||||
interceptMembers(api, { owner: 'john', access_level: 10 });
|
||||
interceptUser(api, { owner: 'john' });
|
||||
|
||||
await assert.rejects(runTasks(gitlab), /User john is not a collaborator for john\/repo/);
|
||||
});
|
||||
|
||||
test('should not make requests in dry run', async t => {
|
||||
const [host, owner, repo] = ['https://gitlab.example.org', 'user', 'repo'];
|
||||
const pushRepo = `${host}/${owner}/${repo}`;
|
||||
const options = { 'dry-run': true, git: { pushRepo }, gitlab: { releaseName: 'R', tokenRef } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
t.mock.method(gitlab, 'getLatestVersion', () => Promise.resolve('1.0.0'));
|
||||
|
||||
await runTasks(gitlab);
|
||||
|
||||
const { isReleased, releaseUrl } = gitlab.getContext();
|
||||
|
||||
assert.equal(gitlab.log.exec.mock.calls[2].arguments[0], 'gitlab releases#uploadAssets');
|
||||
assert.equal(gitlab.log.exec.mock.calls[3].arguments[0], 'gitlab releases#createRelease "R" (1.0.1)');
|
||||
assert(isReleased);
|
||||
assert.equal(releaseUrl, `${pushRepo}/-/releases/1.0.1`);
|
||||
});
|
||||
|
||||
test('should skip checks', async () => {
|
||||
const options = { gitlab: { tokenRef, skipChecks: true, release: true, milestones: ['v1.0.0'] } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
|
||||
await assert.doesNotReject(gitlab.init());
|
||||
await assert.doesNotReject(gitlab.beforeRelease());
|
||||
|
||||
assert.equal(
|
||||
gitlab.log.exec.mock.calls
|
||||
.flatMap(call => call.arguments)
|
||||
.filter(entry => /checkReleaseMilestones/.test(entry[0])).length,
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
test('should not create fetch agent', async () => {
|
||||
const options = { gitlab: {} };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
|
||||
assert.deepEqual(gitlab.certificateAuthorityOption, {});
|
||||
});
|
||||
|
||||
test('should create fetch agent if secure == false', async () => {
|
||||
const options = { gitlab: { secure: false } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
const { dispatcher } = gitlab.certificateAuthorityOption;
|
||||
|
||||
assert(dispatcher instanceof Agent, "Fetch dispatcher should be an instance of undici's Agent class");
|
||||
|
||||
const kOptions = Object.getOwnPropertySymbols(dispatcher).find(symbol => symbol.description === 'options');
|
||||
assert.deepEqual(dispatcher[kOptions].connect, { rejectUnauthorized: false, ca: undefined });
|
||||
});
|
||||
|
||||
test('should create fetch agent if certificateAuthorityFile', async t => {
|
||||
const readFileSync = t.mock.method(fs, 'readFileSync', () => 'test certificate');
|
||||
|
||||
const options = { gitlab: { certificateAuthorityFile: 'cert.crt' } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
const { dispatcher } = gitlab.certificateAuthorityOption;
|
||||
|
||||
assert(dispatcher instanceof Agent, "Fetch dispatcher should be an instance of undici's Agent class");
|
||||
|
||||
const kOptions = Object.getOwnPropertySymbols(dispatcher).find(symbol => symbol.description === 'options');
|
||||
assert.deepEqual(dispatcher[kOptions].connect, { rejectUnauthorized: undefined, ca: 'test certificate' });
|
||||
|
||||
readFileSync.mock.restore();
|
||||
});
|
||||
|
||||
test('should create fetch agent if CI_SERVER_TLS_CA_FILE env is set', async t => {
|
||||
const readFileSync = t.mock.method(fs, 'readFileSync', () => 'test certificate');
|
||||
process.env[certificateAuthorityFileRef] = 'ca.crt';
|
||||
|
||||
const options = { gitlab: {} };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
const { dispatcher } = gitlab.certificateAuthorityOption;
|
||||
|
||||
assert(dispatcher instanceof Agent, "Fetch dispatcher should be an instance of undici's Agent class");
|
||||
|
||||
const kOptions = Object.getOwnPropertySymbols(dispatcher).find(symbol => symbol.description === 'options');
|
||||
assert.deepEqual(dispatcher[kOptions].connect, { rejectUnauthorized: undefined, ca: 'test certificate' });
|
||||
|
||||
readFileSync.mock.restore();
|
||||
});
|
||||
|
||||
test('should create fetch agent if certificateAuthorityFileRef env is set', async t => {
|
||||
const readFileSync = t.mock.method(fs, 'readFileSync', () => 'test certificate');
|
||||
process.env['GITLAB_CA_FILE'] = 'custom-ca.crt';
|
||||
|
||||
const options = { gitlab: { certificateAuthorityFileRef: 'GITLAB_CA_FILE' } };
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
const { dispatcher } = gitlab.certificateAuthorityOption;
|
||||
|
||||
assert(dispatcher instanceof Agent, "Fetch dispatcher should be an instance of undici's Agent class");
|
||||
|
||||
const kOptions = Object.getOwnPropertySymbols(dispatcher).find(symbol => symbol.description === 'options');
|
||||
assert.deepEqual(dispatcher[kOptions].connect, { rejectUnauthorized: undefined, ca: 'test certificate' });
|
||||
|
||||
readFileSync.mock.restore();
|
||||
});
|
||||
|
||||
test('should throw for insecure connections to self-hosted instances', async t => {
|
||||
const host = 'https://localhost:3000';
|
||||
|
||||
const options = {
|
||||
git: { pushRepo: `${host}/user/repo` },
|
||||
gitlab: { host, tokenRef, origin: host }
|
||||
};
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
const server = new GitlabTestServer();
|
||||
|
||||
t.after(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
await server.run();
|
||||
|
||||
await assert.rejects(gitlab.init(), /Could not authenticate with GitLab using environment variable "GITLAB_TOKEN"/);
|
||||
});
|
||||
|
||||
test('should succesfully connect to self-hosted instance if insecure connection allowed', async t => {
|
||||
const host = 'https://localhost:3000';
|
||||
|
||||
const options = {
|
||||
git: { pushRepo: `${host}/user/repo` },
|
||||
gitlab: {
|
||||
host,
|
||||
tokenRef,
|
||||
origin: host,
|
||||
secure: false
|
||||
}
|
||||
};
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
const server = new GitlabTestServer();
|
||||
|
||||
t.after(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
await server.run();
|
||||
|
||||
interceptUser(local);
|
||||
interceptCollaborator(local);
|
||||
|
||||
await assert.doesNotReject(gitlab.init());
|
||||
});
|
||||
|
||||
test('should succesfully connect to self-hosted instance with valid CA file', async t => {
|
||||
const host = 'https://localhost:3000';
|
||||
|
||||
const options = {
|
||||
git: { pushRepo: `${host}/user/repo` },
|
||||
gitlab: {
|
||||
host,
|
||||
tokenRef,
|
||||
origin: host,
|
||||
certificateAuthorityFile: 'test/util/https-server/client/my-private-root-ca.cert.pem'
|
||||
}
|
||||
};
|
||||
const gitlab = await factory(GitLab, { options });
|
||||
const server = new GitlabTestServer();
|
||||
|
||||
t.after(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
await server.run();
|
||||
|
||||
interceptUser(local);
|
||||
interceptCollaborator(local);
|
||||
|
||||
await assert.doesNotReject(gitlab.init());
|
||||
});
|
||||
});
|
||||
146
node_modules/release-it/test/log.js
generated
vendored
Normal file
146
node_modules/release-it/test/log.js
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
import { EOL } from 'node:os';
|
||||
import test, { describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { stripVTControlCharacters } from 'node:util';
|
||||
import mockStdIo from 'mock-stdio';
|
||||
import Log from '../lib/log.js';
|
||||
|
||||
describe('log', () => {
|
||||
test('should write to stdout', () => {
|
||||
const log = new Log();
|
||||
mockStdIo.start();
|
||||
log.log('foo');
|
||||
const { stdout, stderr } = mockStdIo.end();
|
||||
assert.equal(stdout, 'foo\n');
|
||||
assert.equal(stderr, '');
|
||||
});
|
||||
|
||||
test('should write to stderr', () => {
|
||||
const log = new Log();
|
||||
mockStdIo.start();
|
||||
log.error('foo');
|
||||
const { stdout, stderr } = mockStdIo.end();
|
||||
assert.equal(stdout, '');
|
||||
assert.equal(stripVTControlCharacters(stderr), 'ERROR foo\n');
|
||||
});
|
||||
|
||||
test('should print a warning', () => {
|
||||
const log = new Log();
|
||||
mockStdIo.start();
|
||||
log.warn('foo');
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stripVTControlCharacters(stderr), 'WARNING foo\n');
|
||||
});
|
||||
|
||||
test('should print verbose', () => {
|
||||
const log = new Log({ isVerbose: true, verbosityLevel: 2 });
|
||||
mockStdIo.start();
|
||||
log.verbose('foo');
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr, 'foo\n');
|
||||
});
|
||||
|
||||
test('should print external scripts verbose', () => {
|
||||
const log = new Log({ isVerbose: true });
|
||||
mockStdIo.start();
|
||||
log.verbose('foo', { isExternal: true });
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr, 'foo\n');
|
||||
});
|
||||
|
||||
test('should always print external scripts verbose', () => {
|
||||
const log = new Log({ isVerbose: true, verbosityLevel: 2 });
|
||||
mockStdIo.start();
|
||||
log.verbose('foo', { isExternal: true });
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr, 'foo\n');
|
||||
});
|
||||
|
||||
test('should not print verbose by default', () => {
|
||||
const log = new Log();
|
||||
mockStdIo.start();
|
||||
log.verbose('foo');
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr, '');
|
||||
});
|
||||
|
||||
test('should not print command execution by default', () => {
|
||||
const log = new Log();
|
||||
mockStdIo.start();
|
||||
log.exec('foo');
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr.trim(), '');
|
||||
});
|
||||
|
||||
test('should print command execution (verbose)', () => {
|
||||
const log = new Log({ isVerbose: true, verbosityLevel: 2 });
|
||||
mockStdIo.start();
|
||||
log.exec('foo');
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr.trim(), '$ foo');
|
||||
});
|
||||
|
||||
test('should print command execution (verbose/dry run)', () => {
|
||||
const log = new Log({ isVerbose: true });
|
||||
mockStdIo.start();
|
||||
log.exec('foo', { isDryRun: true, isExternal: true });
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr.trim(), '! foo');
|
||||
});
|
||||
|
||||
test('should print command execution (verbose/external)', () => {
|
||||
const log = new Log({ isVerbose: true });
|
||||
mockStdIo.start();
|
||||
log.exec('foo', { isExternal: true });
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr.trim(), '$ foo');
|
||||
});
|
||||
|
||||
test('should print command execution (dry run)', () => {
|
||||
const log = new Log({ isDryRun: true });
|
||||
mockStdIo.start();
|
||||
log.exec('foo');
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr, '$ foo\n');
|
||||
});
|
||||
|
||||
test('should print command execution (read-only)', () => {
|
||||
const log = new Log({ isDryRun: true });
|
||||
mockStdIo.start();
|
||||
log.exec('foo', 'bar', false);
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr, '$ foo bar\n');
|
||||
});
|
||||
|
||||
test('should print command execution (write)', () => {
|
||||
const log = new Log({ isDryRun: true });
|
||||
mockStdIo.start();
|
||||
log.exec('foo', '--arg n', { isDryRun: true });
|
||||
const { stderr } = mockStdIo.end();
|
||||
assert.equal(stderr, '! foo --arg n\n');
|
||||
});
|
||||
|
||||
test('should print obtrusive', () => {
|
||||
const log = new Log({ isCI: false });
|
||||
mockStdIo.start();
|
||||
log.obtrusive('spacious');
|
||||
const { stdout } = mockStdIo.end();
|
||||
assert.equal(stdout, '\nspacious\n\n');
|
||||
});
|
||||
|
||||
test('should not print obtrusive in CI mode', () => {
|
||||
const log = new Log({ isCI: true });
|
||||
mockStdIo.start();
|
||||
log.obtrusive('normal');
|
||||
const { stdout } = mockStdIo.end();
|
||||
assert.equal(stdout, 'normal\n');
|
||||
});
|
||||
|
||||
test('should print preview', () => {
|
||||
const log = new Log();
|
||||
mockStdIo.start();
|
||||
log.preview({ title: 'title', text: 'changelog' });
|
||||
const { stdout } = mockStdIo.end();
|
||||
assert.equal(stripVTControlCharacters(stdout), `Title:${EOL}changelog\n`);
|
||||
});
|
||||
});
|
||||
360
node_modules/release-it/test/npm.js
generated
vendored
Normal file
360
node_modules/release-it/test/npm.js
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
import { join } from 'node:path';
|
||||
import test, { describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import npm from '../lib/plugin/npm/npm.js';
|
||||
import { factory, runTasks } from './util/index.js';
|
||||
import { mkTmpDir, getArgs } from './util/helpers.js';
|
||||
|
||||
describe('npm', async () => {
|
||||
test('should return npm package url', async () => {
|
||||
const options = { npm: { name: 'my-cool-package' } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
assert.equal(npmClient.getPackageUrl(), 'https://www.npmjs.com/package/my-cool-package');
|
||||
});
|
||||
|
||||
test('should return npm package url (custom registry)', async () => {
|
||||
const options = { npm: { name: 'my-cool-package', publishConfig: { registry: 'https://registry.example.org/' } } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
assert.equal(npmClient.getPackageUrl(), 'https://registry.example.org/package/my-cool-package');
|
||||
});
|
||||
|
||||
test('should return npm package url (custom publicPath)', async () => {
|
||||
const options = { npm: { name: 'my-cool-package', publishConfig: { publicPath: '/custom/public-path' } } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
assert.equal(npmClient.getPackageUrl(), 'https://www.npmjs.com/custom/public-path/my-cool-package');
|
||||
});
|
||||
|
||||
test('should return npm package url (custom registry and publicPath)', async () => {
|
||||
const options = {
|
||||
npm: {
|
||||
name: 'my-cool-package',
|
||||
publishConfig: { registry: 'https://registry.example.org/', publicPath: '/custom/public-path' }
|
||||
}
|
||||
};
|
||||
const npmClient = await factory(npm, { options });
|
||||
assert.equal(npmClient.getPackageUrl(), 'https://registry.example.org/custom/public-path/my-cool-package');
|
||||
});
|
||||
|
||||
test('should return default tag', async () => {
|
||||
const npmClient = await factory(npm);
|
||||
const tag = await npmClient.resolveTag();
|
||||
assert.equal(tag, 'latest');
|
||||
});
|
||||
|
||||
test('should resolve default tag for pre-release', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient, 'getRegistryPreReleaseTags', () => []);
|
||||
const tag = await npmClient.resolveTag('1.0.0-0');
|
||||
assert.equal(tag, 'next');
|
||||
});
|
||||
|
||||
test('should guess tag from registry for pre-release', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient, 'getRegistryPreReleaseTags', () => ['alpha']);
|
||||
const tag = await npmClient.resolveTag('1.0.0-0');
|
||||
assert.equal(tag, 'alpha');
|
||||
});
|
||||
|
||||
test('should derive tag from pre-release version', async () => {
|
||||
const npmClient = await factory(npm);
|
||||
const tag = await npmClient.resolveTag('1.0.2-alpha.3');
|
||||
assert.equal(tag, 'alpha');
|
||||
});
|
||||
|
||||
test('should use provided (default) tag even for pre-release', async t => {
|
||||
const options = { npm: { tag: 'latest' } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
await npmClient.bump('1.0.0-next.0');
|
||||
assert.equal(npmClient.getContext('tag'), 'latest');
|
||||
});
|
||||
|
||||
test('should throw when `npm version` fails', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient.shell, 'exec', () =>
|
||||
Promise.reject(new Error('npm ERR! Version not changed, might want --allow-same-version'))
|
||||
);
|
||||
await assert.rejects(npmClient.bump('1.0.0-next.0'), { message: /Version not changed/ });
|
||||
});
|
||||
|
||||
test('should return first pre-release tag from package in registry when resolving tag without pre-id', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
const response = { latest: '1.4.1', alpha: '2.0.0-alpha.1', beta: '2.0.0-beta.3' };
|
||||
t.mock.method(npmClient.shell, 'exec', () => Promise.resolve(JSON.stringify(response)));
|
||||
assert.equal(await npmClient.resolveTag('2.0.0-5'), 'alpha');
|
||||
});
|
||||
|
||||
test('should return default pre-release tag when resolving tag without pre-id', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
const response = {
|
||||
latest: '1.4.1'
|
||||
};
|
||||
t.mock.method(npmClient.shell, 'exec', () => Promise.resolve(JSON.stringify(response)));
|
||||
assert.equal(await npmClient.resolveTag('2.0.0-0'), 'next');
|
||||
});
|
||||
|
||||
test('should handle erroneous output when resolving tag without pre-id', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient.shell, 'exec', () => Promise.resolve(''));
|
||||
assert.equal(await npmClient.resolveTag('2.0.0-0'), 'next');
|
||||
});
|
||||
|
||||
test('should handle errored request when resolving tag without pre-id', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
assert.equal(await npmClient.resolveTag('2.0.0-0'), 'next');
|
||||
});
|
||||
|
||||
test('should add registry to commands when specified', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
npmClient.setContext({ publishConfig: { registry: 'registry.example.org' } });
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm whoami --registry registry.example.org') return Promise.resolve('john');
|
||||
const re = /npm access (list collaborators --json|ls-collaborators) release-it --registry registry.example.org/;
|
||||
if (re.test.command) return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await runTasks(npmClient);
|
||||
assert.equal(exec.mock.calls[0].arguments[0], 'npm ping --registry registry.example.org');
|
||||
assert.equal(exec.mock.calls[1].arguments[0], 'npm whoami --registry registry.example.org');
|
||||
assert.match(
|
||||
exec.mock.calls[2].arguments[0],
|
||||
/npm show release-it@[a-z]+ version --registry registry\.example\.org/
|
||||
);
|
||||
});
|
||||
|
||||
test('should not throw when executing tasks', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm whoami') return Promise.resolve('john');
|
||||
const re = /npm access (list collaborators --json|ls-collaborators) release-it/;
|
||||
if (re.test.command) return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
return Promise.resolve();
|
||||
});
|
||||
await assert.doesNotReject(runTasks(npmClient));
|
||||
});
|
||||
|
||||
test('should throw if npm is down', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm ping') return Promise.reject();
|
||||
return Promise.resolve();
|
||||
});
|
||||
await assert.rejects(runTasks(npmClient), { message: /^Unable to reach npm registry/ });
|
||||
});
|
||||
|
||||
test('should not throw if npm returns 400/404 for unsupported ping/whoami/access', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
const pingError = "npm ERR! code E404\nnpm ERR! 404 Package '--ping' not found : ping";
|
||||
const whoamiError = "npm ERR! code E404\nnpm ERR! 404 Package '--whoami' not found : whoami";
|
||||
const accessError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/collaborators';
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error(pingError)), 0);
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error(whoamiError)), 1);
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error(accessError)), 2);
|
||||
await runTasks(npmClient);
|
||||
assert.equal(exec.mock.calls.at(-1).arguments[0].trim(), 'npm publish . --tag latest');
|
||||
});
|
||||
|
||||
test('should not throw if npm returns 400 for unsupported ping/whoami/access', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
const pingError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/ping?write=true';
|
||||
const whoamiError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/whoami';
|
||||
const accessError = 'npm ERR! code E400\nnpm ERR! 400 Bad Request - GET https://npm.example.org/-/collaborators';
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error(pingError)), 0);
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error(whoamiError)), 1);
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error(accessError)), 2);
|
||||
await runTasks(npmClient);
|
||||
assert.equal(exec.mock.calls.at(-1).arguments[0].trim(), 'npm publish . --tag latest');
|
||||
});
|
||||
|
||||
test('should throw if user is not authenticated', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(), 1);
|
||||
await assert.rejects(runTasks(npmClient), { message: /^Not authenticated with npm/ });
|
||||
});
|
||||
|
||||
test('should throw if user is not a collaborator (v9)', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm whoami') return Promise.resolve('ada');
|
||||
if (command === 'npm --version') return Promise.resolve('9.2.0');
|
||||
if (command === 'npm access list collaborators --json release-it')
|
||||
return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
return Promise.resolve();
|
||||
});
|
||||
await assert.rejects(runTasks(npmClient), { message: /^User ada is not a collaborator for release-it/ });
|
||||
});
|
||||
|
||||
test('should throw if user is not a collaborator (v8)', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
|
||||
t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm whoami') return Promise.resolve('ada');
|
||||
if (command === 'npm --version') return Promise.resolve('8.2.0');
|
||||
const re = /npm access (list collaborators --json|ls-collaborators) release-it/;
|
||||
if (re.test(command)) return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await assert.rejects(runTasks(npmClient), { message: /^User ada is not a collaborator for release-it/ });
|
||||
});
|
||||
|
||||
test('should not throw if user is not a collaborator on a new package', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
|
||||
t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm whoami') return Promise.resolve('ada');
|
||||
const re = /npm access (list collaborators --json|ls-collaborators) release-it/;
|
||||
const message =
|
||||
'npm ERR! code E404\nnpm ERR! 404 Not Found - GET https://registry.npmjs.org/-/package/release-it/collaborators?format=cli - File not found';
|
||||
if (re.test(command)) return Promise.reject(new Error(message));
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await assert.doesNotReject(runTasks(npmClient));
|
||||
});
|
||||
|
||||
test('should handle 2FA and publish with OTP', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
npmClient.setContext({ name: 'pkg' });
|
||||
|
||||
const exec = t.mock.method(npmClient.shell, 'exec');
|
||||
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error('Initial error with one-time pass.')), 0);
|
||||
exec.mock.mockImplementationOnce(() => Promise.reject(new Error('The provided one-time pass is incorrect.')), 1);
|
||||
exec.mock.mockImplementationOnce(() => Promise.resolve(), 2);
|
||||
|
||||
await npmClient.publish({
|
||||
otpCallback: () =>
|
||||
npmClient.publish({
|
||||
otp: '123',
|
||||
otpCallback: () => npmClient.publish({ otp: '123456' })
|
||||
})
|
||||
});
|
||||
|
||||
assert.equal(exec.mock.callCount(), 3);
|
||||
assert.equal(exec.mock.calls[0].arguments[0].trim(), 'npm publish . --tag latest');
|
||||
assert.equal(exec.mock.calls[1].arguments[0].trim(), 'npm publish . --tag latest --otp 123');
|
||||
assert.equal(exec.mock.calls[2].arguments[0].trim(), 'npm publish . --tag latest --otp 123456');
|
||||
|
||||
assert.equal(npmClient.log.warn.mock.callCount(), 1);
|
||||
assert.equal(npmClient.log.warn.mock.calls[0].arguments[0], 'The provided OTP is incorrect or has expired.');
|
||||
});
|
||||
|
||||
test('should publish', async t => {
|
||||
const npmClient = await factory(npm);
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm whoami') return Promise.resolve('john');
|
||||
const re = /npm access (list collaborators --json|ls-collaborators) release-it/;
|
||||
if (re.test(command)) return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
return Promise.resolve();
|
||||
});
|
||||
await runTasks(npmClient);
|
||||
assert.equal(exec.mock.calls.at(-1).arguments[0].trim(), 'npm publish . --tag latest');
|
||||
});
|
||||
|
||||
test('should use extra publish arguments', async t => {
|
||||
const options = { npm: { skipChecks: true, publishArgs: '--registry=http://my-internal-registry.local' } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
await runTasks(npmClient);
|
||||
assert.equal(
|
||||
exec.mock.calls.at(-1).arguments[0].trim(),
|
||||
'npm publish . --tag latest --registry=http://my-internal-registry.local'
|
||||
);
|
||||
});
|
||||
|
||||
test('should skip checks', async () => {
|
||||
const options = { npm: { skipChecks: true } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
await assert.doesNotReject(npmClient.init());
|
||||
});
|
||||
|
||||
test('should publish to a different/scoped registry', async t => {
|
||||
const tmp = mkTmpDir();
|
||||
process.chdir(tmp);
|
||||
writeFileSync(
|
||||
join(tmp, 'package.json'),
|
||||
JSON.stringify({
|
||||
name: '@my-scope/my-pkg',
|
||||
version: '1.0.0',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
'@my-scope:registry': 'https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/'
|
||||
}
|
||||
})
|
||||
);
|
||||
const options = { npm };
|
||||
const npmClient = await factory(npm, { options });
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', command => {
|
||||
const cmd = 'npm whoami --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/';
|
||||
if (command === cmd) return Promise.resolve('john');
|
||||
const re =
|
||||
/npm access (list collaborators --json|ls-collaborators) @my-scope\/my-pkg --registry https:\/\/gitlab\.com\/api\/v4\/projects\/my-scope%2Fmy-pkg\/packages\/npm\//;
|
||||
if (re.test(command)) return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
return Promise.resolve();
|
||||
});
|
||||
await runTasks(npmClient);
|
||||
|
||||
assert.deepEqual(getArgs(exec, 'npm'), [
|
||||
'npm ping --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/',
|
||||
'npm whoami --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/',
|
||||
'npm show @my-scope/my-pkg@latest version --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/',
|
||||
'npm --version',
|
||||
'npm version 1.0.1 --no-git-tag-version',
|
||||
'npm publish . --tag latest --registry https://gitlab.com/api/v4/projects/my-scope%2Fmy-pkg/packages/npm/'
|
||||
]);
|
||||
});
|
||||
|
||||
test('should not publish when `npm version` fails', async t => {
|
||||
const tmp = mkTmpDir();
|
||||
process.chdir(tmp);
|
||||
writeFileSync(join(tmp, 'package.json'), JSON.stringify({ name: '@my-scope/my-pkg', version: '1.0.0' }));
|
||||
const options = { npm };
|
||||
const npmClient = await factory(npm, { options });
|
||||
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', command => {
|
||||
if (command === 'npm whoami') return Promise.resolve('john');
|
||||
const re = /npm access (list collaborators --json|ls-collaborators) @my-scope\/my-pkg/;
|
||||
if (re.test(command)) return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
if (command === 'npm version 1.0.1 --no-git-tag-version')
|
||||
return Promise.reject('npm ERR! Version not changed, might want --allow-same-version');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await assert.rejects(runTasks(npmClient), /Version not changed/);
|
||||
|
||||
assert.deepEqual(getArgs(exec, 'npm'), [
|
||||
'npm ping',
|
||||
'npm whoami',
|
||||
'npm show @my-scope/my-pkg@latest version',
|
||||
'npm --version',
|
||||
'npm version 1.0.1 --no-git-tag-version'
|
||||
]);
|
||||
});
|
||||
|
||||
test('should add allow-same-version argument', async t => {
|
||||
const options = { npm: { skipChecks: true, allowSameVersion: true } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
|
||||
await runTasks(npmClient);
|
||||
const versionArgs = getArgs(exec, 'npm version');
|
||||
assert.match(versionArgs[0], / --allow-same-version/);
|
||||
});
|
||||
|
||||
test('should add version arguments', async t => {
|
||||
const options = { npm: { skipChecks: true, versionArgs: ['--workspaces-update=false', '--allow-same-version'] } };
|
||||
const npmClient = await factory(npm, { options });
|
||||
const exec = t.mock.method(npmClient.shell, 'exec', () => Promise.resolve());
|
||||
await runTasks(npmClient);
|
||||
const versionArgs = getArgs(exec, 'npm version');
|
||||
assert.match(versionArgs[0], / --workspaces-update=false --allow-same-version/);
|
||||
});
|
||||
});
|
||||
10
node_modules/release-it/test/plugin-name.js
generated
vendored
Normal file
10
node_modules/release-it/test/plugin-name.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { getPluginName } from '../lib/plugin/factory.js';
|
||||
|
||||
test('pluginName can return correct name for variants', async () => {
|
||||
assert.equal(getPluginName('plain-plugin'), 'plain-plugin');
|
||||
assert.equal(getPluginName('@some/scoped-plugin'), '@some/scoped-plugin');
|
||||
assert.equal(getPluginName('@some/nested/scoped-plugin'), '@some/nested/scoped-plugin');
|
||||
assert.equal(getPluginName('./relative-plugin.cjs'), 'relative-plugin');
|
||||
});
|
||||
232
node_modules/release-it/test/plugins.js
generated
vendored
Normal file
232
node_modules/release-it/test/plugins.js
generated
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
import { resolve, join } from 'node:path';
|
||||
import childProcess from 'node:child_process';
|
||||
import fs, { appendFileSync, mkdirSync } from 'node:fs';
|
||||
import test, { afterEach, describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import Config from '../lib/config.js';
|
||||
import { execOpts, parseGitUrl } from '../lib/util.js';
|
||||
import runTasks from '../lib/index.js';
|
||||
import MyPlugin from './stub/plugin.js';
|
||||
import ReplacePlugin from './stub/plugin-replace.js';
|
||||
import ContextPlugin from './stub/plugin-context.js';
|
||||
import { getArgs, mkTmpDir } from './util/helpers.js';
|
||||
import ShellStub from './stub/shell.js';
|
||||
import { LogStub, SpinnerStub } from './util/index.js';
|
||||
|
||||
describe('plugins', () => {
|
||||
const testConfig = {
|
||||
ci: true,
|
||||
config: false
|
||||
};
|
||||
|
||||
const log = new LogStub();
|
||||
const spinner = new SpinnerStub();
|
||||
|
||||
const getContainer = options => {
|
||||
const config = new Config(Object.assign({}, testConfig, options));
|
||||
const shell = new ShellStub({ container: { log, config } });
|
||||
return { log, spinner, config, shell };
|
||||
};
|
||||
|
||||
childProcess.execSync('npm link', execOpts);
|
||||
|
||||
afterEach(() => {
|
||||
log.resetCalls();
|
||||
});
|
||||
|
||||
test('should instantiate plugins and execute all release-cycle methods', async () => {
|
||||
const pluginDir = mkTmpDir();
|
||||
const dir = mkTmpDir();
|
||||
process.chdir(pluginDir);
|
||||
|
||||
appendFileSync(
|
||||
join(pluginDir, 'package.json'),
|
||||
JSON.stringify({ name: 'my-plugin', version: '1.0.0', type: 'module' })
|
||||
);
|
||||
childProcess.execSync(`npm link release-it`, execOpts);
|
||||
const content = "import { Plugin } from 'release-it'; " + MyPlugin.toString() + '; export default MyPlugin;';
|
||||
|
||||
appendFileSync(join(pluginDir, 'index.js'), content);
|
||||
process.chdir(dir);
|
||||
mkdirSync(resolve('my/plugin'), { recursive: true });
|
||||
process.chdir('my/plugin');
|
||||
|
||||
appendFileSync(join(dir, 'my', 'plugin', 'index.js'), content);
|
||||
process.chdir(dir);
|
||||
|
||||
appendFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'project', version: '1.0.0', type: 'module' }));
|
||||
childProcess.execSync(`npm install ${pluginDir}`, execOpts);
|
||||
childProcess.execSync(`npm link release-it`, execOpts);
|
||||
|
||||
const config = {
|
||||
plugins: {
|
||||
'my-plugin': {
|
||||
name: 'foo'
|
||||
},
|
||||
'./my/plugin/index.js': [
|
||||
'named-plugin',
|
||||
{
|
||||
name: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const container = getContainer(config);
|
||||
|
||||
const result = await runTasks({}, container);
|
||||
|
||||
assert.deepEqual(
|
||||
container.log.info.mock.calls.map(call => call.arguments),
|
||||
[
|
||||
['my-plugin:foo:init'],
|
||||
['named-plugin:bar:init'],
|
||||
['my-plugin:foo:getName'],
|
||||
['my-plugin:foo:getLatestVersion'],
|
||||
['my-plugin:foo:getIncrement'],
|
||||
['my-plugin:foo:getIncrementedVersionCI'],
|
||||
['named-plugin:bar:getIncrementedVersionCI'],
|
||||
['my-plugin:foo:beforeBump'],
|
||||
['named-plugin:bar:beforeBump'],
|
||||
['my-plugin:foo:bump:1.3.0'],
|
||||
['named-plugin:bar:bump:1.3.0'],
|
||||
['my-plugin:foo:beforeRelease'],
|
||||
['named-plugin:bar:beforeRelease'],
|
||||
['my-plugin:foo:release'],
|
||||
['named-plugin:bar:release'],
|
||||
['my-plugin:foo:afterRelease'],
|
||||
['named-plugin:bar:afterRelease']
|
||||
]
|
||||
);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
changelog: undefined,
|
||||
name: 'new-project-name',
|
||||
latestVersion: '1.2.3',
|
||||
version: '1.3.0'
|
||||
});
|
||||
});
|
||||
|
||||
test('should instantiate plugins and execute all release-cycle methods for scoped plugins', async () => {
|
||||
const pluginDir = mkTmpDir();
|
||||
const dir = mkTmpDir();
|
||||
process.chdir(pluginDir);
|
||||
|
||||
fs.writeFileSync(
|
||||
join(pluginDir, 'package.json'),
|
||||
JSON.stringify({ name: '@scoped/my-plugin', version: '1.0.0', type: 'module' })
|
||||
);
|
||||
childProcess.execSync(`npm link release-it`, execOpts);
|
||||
const content = "import { Plugin } from 'release-it'; " + MyPlugin.toString() + '; export default MyPlugin;';
|
||||
|
||||
fs.writeFileSync(join(pluginDir, 'index.js'), content);
|
||||
process.chdir(dir);
|
||||
|
||||
fs.writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'project', version: '1.0.0', type: 'module' }));
|
||||
childProcess.execSync(`npm install ${pluginDir}`, execOpts);
|
||||
childProcess.execSync(`npm link release-it`, execOpts);
|
||||
|
||||
const config = {
|
||||
plugins: {
|
||||
'@scoped/my-plugin': {
|
||||
name: 'foo'
|
||||
}
|
||||
}
|
||||
};
|
||||
const container = getContainer(config);
|
||||
|
||||
const result = await runTasks({}, container);
|
||||
|
||||
assert.deepEqual(
|
||||
container.log.info.mock.calls.map(call => call.arguments),
|
||||
[
|
||||
['@scoped/my-plugin:foo:init'],
|
||||
['@scoped/my-plugin:foo:getName'],
|
||||
['@scoped/my-plugin:foo:getLatestVersion'],
|
||||
['@scoped/my-plugin:foo:getIncrement'],
|
||||
['@scoped/my-plugin:foo:getIncrementedVersionCI'],
|
||||
['@scoped/my-plugin:foo:beforeBump'],
|
||||
['@scoped/my-plugin:foo:bump:1.3.0'],
|
||||
['@scoped/my-plugin:foo:beforeRelease'],
|
||||
['@scoped/my-plugin:foo:release'],
|
||||
['@scoped/my-plugin:foo:afterRelease']
|
||||
]
|
||||
);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
changelog: undefined,
|
||||
name: 'new-project-name',
|
||||
latestVersion: '1.2.3',
|
||||
version: '1.3.0'
|
||||
});
|
||||
});
|
||||
|
||||
test('should disable core plugins', async () => {
|
||||
const dir = mkTmpDir();
|
||||
process.chdir(dir);
|
||||
|
||||
fs.appendFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'project', version: '1.0.0' }));
|
||||
const content =
|
||||
"import { Plugin } from 'release-it'; " + ReplacePlugin.toString() + '; export default ReplacePlugin;';
|
||||
|
||||
fs.appendFileSync(join(dir, 'replace-plugin.mjs'), content);
|
||||
childProcess.execSync(`npm link release-it`, execOpts);
|
||||
|
||||
const config = {
|
||||
plugins: {
|
||||
'./replace-plugin.mjs': {}
|
||||
}
|
||||
};
|
||||
const container = getContainer(config);
|
||||
|
||||
const result = await runTasks({}, container);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
changelog: undefined,
|
||||
name: undefined,
|
||||
latestVersion: '0.0.0',
|
||||
version: undefined
|
||||
});
|
||||
});
|
||||
|
||||
test('should expose context to execute commands', async t => {
|
||||
const dir = mkTmpDir();
|
||||
process.chdir(dir);
|
||||
|
||||
fs.appendFileSync(
|
||||
join(dir, 'package.json'),
|
||||
JSON.stringify({ name: 'pkg-name', version: '1.0.0', type: 'module' })
|
||||
);
|
||||
const content =
|
||||
"import { Plugin } from 'release-it'; " + ContextPlugin.toString() + '; export default ContextPlugin;';
|
||||
|
||||
fs.appendFileSync(join(dir, 'context-plugin.js'), content);
|
||||
childProcess.execSync(`npm link release-it`, execOpts);
|
||||
|
||||
const repo = parseGitUrl('https://github.com/user/pkg');
|
||||
|
||||
const container = getContainer({ plugins: { './context-plugin.js': {} } });
|
||||
const exec = t.mock.method(container.shell, 'execFormattedCommand');
|
||||
|
||||
container.config.setContext({ repo });
|
||||
container.config.setContext({ tagName: '1.0.1' });
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const pluginExecArgs = getArgs(exec, 'echo');
|
||||
|
||||
assert.deepEqual(pluginExecArgs, [
|
||||
'echo false',
|
||||
'echo false',
|
||||
`echo pkg-name user 1.0.0 1.0.1`,
|
||||
`echo pkg-name user 1.0.0 1.0.1`,
|
||||
`echo user pkg user/pkg 1.0.1`,
|
||||
`echo user pkg user/pkg 1.0.1`,
|
||||
`echo user pkg user/pkg 1.0.1`,
|
||||
`echo user pkg user/pkg 1.0.1`,
|
||||
`echo pkg 1.0.0 1.0.1 1.0.1`,
|
||||
`echo pkg 1.0.0 1.0.1 1.0.1`,
|
||||
`echo pkg 1.0.0 1.0.1 1.0.1`,
|
||||
`echo pkg 1.0.0 1.0.1 1.0.1`
|
||||
]);
|
||||
});
|
||||
});
|
||||
92
node_modules/release-it/test/prompt.js
generated
vendored
Normal file
92
node_modules/release-it/test/prompt.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import Prompt from '../lib/prompt.js';
|
||||
import Config from '../lib/config.js';
|
||||
import git from '../lib/plugin/git/prompts.js';
|
||||
import github from '../lib/plugin/github/prompts.js';
|
||||
import gitlab from '../lib/plugin/gitlab/prompts.js';
|
||||
import npm from '../lib/plugin/npm/prompts.js';
|
||||
import { factory } from './util/index.js';
|
||||
|
||||
const prompts = { git, github, gitlab, npm };
|
||||
|
||||
const yes = ([options]) => Promise.resolve({ [options.name]: true });
|
||||
const no = ([options]) => Promise.resolve({ [options.name]: false });
|
||||
|
||||
test('should not create prompt if disabled', async t => {
|
||||
const task = t.mock.fn();
|
||||
const promptMock = t.mock.fn(yes);
|
||||
const inquirer = { prompt: promptMock };
|
||||
const prompt = await factory(Prompt, { container: { inquirer } });
|
||||
prompt.register(prompts.git);
|
||||
await prompt.show({ enabled: false, prompt: 'push', task });
|
||||
assert.equal(promptMock.mock.callCount(), 0);
|
||||
assert.equal(task.mock.callCount(), 0);
|
||||
});
|
||||
|
||||
test('should create prompt', async t => {
|
||||
const promptMock = t.mock.fn(yes);
|
||||
const inquirer = { prompt: promptMock };
|
||||
const prompt = await factory(Prompt, { container: { inquirer } });
|
||||
prompt.register(prompts.git);
|
||||
await prompt.show({ prompt: 'push' });
|
||||
assert.equal(promptMock.mock.callCount(), 1);
|
||||
assert.deepEqual(promptMock.mock.calls[0].arguments[0][0], {
|
||||
type: 'confirm',
|
||||
message: 'Push?',
|
||||
name: 'push',
|
||||
choices: false,
|
||||
transformer: undefined,
|
||||
default: true
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
['git', 'commit', 'Commit (Release 1.0.0)?'],
|
||||
['git', 'tag', 'Tag (1.0.0)?'],
|
||||
['git', 'push', 'Push?'],
|
||||
['github', 'release', 'Create a pre-release on GitHub (Release 1.0.0)?'],
|
||||
['gitlab', 'release', 'Create a release on GitLab (Release 1.0.0)?'],
|
||||
['npm', 'publish', 'Publish my-pkg@next to npm?'],
|
||||
['npm', 'otp', 'Please enter OTP for npm:']
|
||||
].map(async ([namespace, prompt, message]) => {
|
||||
test(`should create prompt and render template message (${namespace}.${prompt})`, async t => {
|
||||
const promptMock = t.mock.fn(yes);
|
||||
const config = new Config({
|
||||
isPreRelease: true,
|
||||
git: { tagName: 'v${version}' },
|
||||
npm: { name: 'my-pkg', tag: 'next' }
|
||||
});
|
||||
await config.init();
|
||||
config.setContext({ version: '1.0.0', tagName: '1.0.0' });
|
||||
const inquirer = { prompt: promptMock };
|
||||
const p = await factory(Prompt, { container: { inquirer } });
|
||||
p.register(prompts[namespace], namespace);
|
||||
await p.show({ namespace, prompt, context: config.getContext() });
|
||||
assert.equal(promptMock.mock.callCount(), 1);
|
||||
assert.equal(promptMock.mock.calls[0].arguments[0][0].message, message);
|
||||
});
|
||||
});
|
||||
|
||||
test('should execute task after positive answer', async t => {
|
||||
const task = t.mock.fn();
|
||||
const promptMock = t.mock.fn(yes);
|
||||
const inquirer = { prompt: promptMock };
|
||||
const prompt = await factory(Prompt, { container: { inquirer } });
|
||||
prompt.register(prompts.git);
|
||||
await prompt.show({ prompt: 'push', task });
|
||||
assert.equal(promptMock.mock.callCount(), 1);
|
||||
assert.equal(task.mock.callCount(), 1);
|
||||
assert.equal(task.mock.calls[0].arguments[0], true);
|
||||
});
|
||||
|
||||
test('should not execute task after negative answer', async t => {
|
||||
const task = t.mock.fn();
|
||||
const promptMock = t.mock.fn(no);
|
||||
const inquirer = { prompt: promptMock };
|
||||
const prompt = await factory(Prompt, { container: { inquirer } });
|
||||
prompt.register(prompts.git);
|
||||
await prompt.show({ prompt: 'push', task });
|
||||
assert.equal(promptMock.mock.callCount(), 1);
|
||||
assert.equal(task.mock.callCount(), 0);
|
||||
});
|
||||
1
node_modules/release-it/test/resources/file-v2.0.1.txt
generated
vendored
Normal file
1
node_modules/release-it/test/resources/file-v2.0.1.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*
|
||||
1
node_modules/release-it/test/resources/file-v2.0.2.txt
generated
vendored
Normal file
1
node_modules/release-it/test/resources/file-v2.0.2.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*
|
||||
1
node_modules/release-it/test/resources/file1
generated
vendored
Normal file
1
node_modules/release-it/test/resources/file1
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
file1
|
||||
76
node_modules/release-it/test/shell.js
generated
vendored
Normal file
76
node_modules/release-it/test/shell.js
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
import childProcess from 'node:child_process';
|
||||
import test, { describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import Shell from '../lib/shell.js';
|
||||
import { factory } from './util/index.js';
|
||||
|
||||
describe('shell', async () => {
|
||||
const cwd = childProcess.execSync('pwd', { encoding: 'utf8' }).trim();
|
||||
|
||||
const shell = await factory(Shell);
|
||||
|
||||
test('exec', async () => {
|
||||
assert.equal(await shell.exec('echo bar'), 'bar');
|
||||
});
|
||||
|
||||
test('exec (with context)', async () => {
|
||||
const exec = cmd => shell.exec(cmd, { verbose: false }, shell.config.getContext());
|
||||
assert.equal(await exec(''), undefined);
|
||||
assert.equal(await exec('pwd'), cwd);
|
||||
assert.equal(await exec('echo ${git.pushArgs}'), '--follow-tags');
|
||||
assert.equal(await exec('echo -*- ${github.tokenRef} -*-'), '-*- GITHUB_TOKEN -*-');
|
||||
});
|
||||
|
||||
test('exec (with args)', async () => {
|
||||
assert.equal(await shell.exec([]), undefined);
|
||||
assert.equal(await shell.exec(['pwd']), cwd);
|
||||
assert.equal(await shell.exec(['echo', 'a', 'b']), 'a b');
|
||||
assert.equal(await shell.exec(['echo', '"a"']), '"a"');
|
||||
});
|
||||
|
||||
test('exec (dry-run/read-only)', async () => {
|
||||
const shell = await factory(Shell, { options: { 'dry-run': true } });
|
||||
{
|
||||
const actual = await shell.exec('pwd', { write: false });
|
||||
assert.equal(actual, cwd);
|
||||
assert.equal(shell.log.exec.mock.callCount(), 1);
|
||||
assert.equal(shell.log.exec.mock.calls[0].arguments[0], 'pwd');
|
||||
}
|
||||
{
|
||||
const actual = await shell.exec('pwd');
|
||||
assert.equal(actual, undefined);
|
||||
assert.equal(shell.log.exec.mock.callCount(), 2);
|
||||
assert.equal(shell.log.exec.mock.calls[1].arguments[0], 'pwd');
|
||||
assert.deepEqual(shell.log.exec.mock.calls[1].arguments.at(-1), { isDryRun: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('exec (verbose)', async () => {
|
||||
const shell = await factory(Shell, { options: { verbose: true } });
|
||||
const actual = await shell.exec('echo foo');
|
||||
assert.equal(shell.log.exec.mock.calls[0].arguments[0], 'echo foo');
|
||||
assert.equal(shell.log.exec.mock.callCount(), 1);
|
||||
assert.equal(shell.log.verbose.mock.calls[0].arguments[0], 'foo');
|
||||
assert.equal(shell.log.verbose.mock.callCount(), 1);
|
||||
assert.equal(actual, 'foo');
|
||||
});
|
||||
|
||||
test('should cache results of command execution', async () => {
|
||||
const shell = await factory(Shell);
|
||||
const result1 = await shell.exec('echo foo');
|
||||
const result2 = await shell.exec('echo foo');
|
||||
assert(result1 === result2);
|
||||
assert.deepEqual(
|
||||
shell.log.exec.mock.calls.map(call => call.arguments),
|
||||
[
|
||||
['echo foo', { isExternal: false, isCached: false }],
|
||||
['echo foo', { isExternal: false, isCached: true }]
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('should bail out on failed command execution', async () => {
|
||||
const shell = new Shell({ container: {} });
|
||||
await assert.rejects(shell.exec('foo'));
|
||||
});
|
||||
});
|
||||
57
node_modules/release-it/test/spinner.js
generated
vendored
Normal file
57
node_modules/release-it/test/spinner.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import Spinner from '../lib/spinner.js';
|
||||
import Config from '../lib/config.js';
|
||||
|
||||
const getConfig = async options => {
|
||||
const testConfig = {
|
||||
ci: false,
|
||||
config: false
|
||||
};
|
||||
const config = new Config(Object.assign({}, testConfig, options));
|
||||
await config.init();
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
test('should not show spinner and not execute task if disabled', async t => {
|
||||
const ora = t.mock.fn();
|
||||
const task = t.mock.fn();
|
||||
const spinner = new Spinner({ container: { ora } });
|
||||
await spinner.show({ enabled: false, task });
|
||||
assert.equal(task.mock.callCount(), 0);
|
||||
assert.equal(ora.mock.callCount(), 0);
|
||||
});
|
||||
|
||||
test('should show spinner and run task by default', async t => {
|
||||
const ora = t.mock.fn();
|
||||
const task = t.mock.fn(() => Promise.resolve());
|
||||
const label = 'foo';
|
||||
const config = await getConfig({ ci: true });
|
||||
const spinner = new Spinner({ container: { ora, config } });
|
||||
await spinner.show({ task, label });
|
||||
assert.equal(task.mock.callCount(), 1);
|
||||
assert.equal(ora.mock.callCount(), 1);
|
||||
assert.equal(ora.mock.calls[0].arguments[0], task.mock.calls[0].result);
|
||||
assert.equal(ora.mock.calls[0].arguments[1], label);
|
||||
});
|
||||
|
||||
test('should run task, but not show spinner if interactive', async t => {
|
||||
const ora = t.mock.fn();
|
||||
const task = t.mock.fn(() => Promise.resolve());
|
||||
const config = await getConfig({ ci: false });
|
||||
const spinner = new Spinner({ container: { ora, config } });
|
||||
await spinner.show({ task });
|
||||
assert.equal(task.mock.callCount(), 1);
|
||||
assert.equal(ora.mock.callCount(), 0);
|
||||
});
|
||||
|
||||
test('should run task and show spinner if interactive, but external', async t => {
|
||||
const ora = t.mock.fn();
|
||||
const task = t.mock.fn(() => Promise.resolve());
|
||||
const config = await getConfig({ ci: false });
|
||||
const spinner = new Spinner({ container: { ora, config } });
|
||||
await spinner.show({ task, external: true });
|
||||
assert.equal(task.mock.callCount(), 1);
|
||||
assert.equal(ora.mock.callCount(), 1);
|
||||
});
|
||||
5
node_modules/release-it/test/stub/config/default/.release-it.json
generated
vendored
Normal file
5
node_modules/release-it/test/stub/config/default/.release-it.json
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"github": {
|
||||
"release": true
|
||||
}
|
||||
}
|
||||
1
node_modules/release-it/test/stub/config/invalid-config-rc
generated
vendored
Normal file
1
node_modules/release-it/test/stub/config/invalid-config-rc
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
foo=bar
|
||||
2
node_modules/release-it/test/stub/config/invalid-config-txt
generated
vendored
Normal file
2
node_modules/release-it/test/stub/config/invalid-config-txt
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
foo
|
||||
bar\baz
|
||||
5
node_modules/release-it/test/stub/config/merge/.release-it.json
generated
vendored
Normal file
5
node_modules/release-it/test/stub/config/merge/.release-it.json
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"github": {
|
||||
"release": true
|
||||
}
|
||||
}
|
||||
7
node_modules/release-it/test/stub/config/merge/package.json
generated
vendored
Normal file
7
node_modules/release-it/test/stub/config/merge/package.json
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"release-it": {
|
||||
"git": {
|
||||
"push": false
|
||||
}
|
||||
}
|
||||
}
|
||||
5
node_modules/release-it/test/stub/config/remote/.release-it.json
generated
vendored
Normal file
5
node_modules/release-it/test/stub/config/remote/.release-it.json
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "Released version ${version}"
|
||||
}
|
||||
}
|
||||
5
node_modules/release-it/test/stub/config/remote/sub/.release-it.json
generated
vendored
Normal file
5
node_modules/release-it/test/stub/config/remote/sub/.release-it.json
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "Released with version ${version}"
|
||||
}
|
||||
}
|
||||
2
node_modules/release-it/test/stub/config/toml/.release-it.toml
generated
vendored
Normal file
2
node_modules/release-it/test/stub/config/toml/.release-it.toml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[foo]
|
||||
bar=1
|
||||
2
node_modules/release-it/test/stub/config/yaml/.release-it.yaml
generated
vendored
Normal file
2
node_modules/release-it/test/stub/config/yaml/.release-it.yaml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
foo:
|
||||
bar: 1
|
||||
2
node_modules/release-it/test/stub/config/yml/.release-it.yml
generated
vendored
Normal file
2
node_modules/release-it/test/stub/config/yml/.release-it.yml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
foo:
|
||||
bar: 1
|
||||
150
node_modules/release-it/test/stub/github.js
generated
vendored
Normal file
150
node_modules/release-it/test/stub/github.js
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
export const interceptAuthentication = (server, { username = 'john' } = {}) => {
|
||||
server.get('/user', { status: 200, body: { login: username } });
|
||||
};
|
||||
|
||||
export const interceptCollaborator = (server, { owner = 'user', project = 'repo', username = 'john' } = {}) => {
|
||||
server.get(`/repos/${owner}/${project}/collaborators/${username}`, { status: 204 });
|
||||
};
|
||||
|
||||
export const interceptListReleases = (
|
||||
server,
|
||||
{ host = 'github.com', owner = 'user', project = 'repo', tag_name } = {}
|
||||
) => {
|
||||
server.get(`/repos/${owner}/${project}/releases?per_page=1&page=1`, {
|
||||
status: 200,
|
||||
body: [
|
||||
{
|
||||
id: 1,
|
||||
upload_url: `https://uploads.${host}/repos/${owner}/${project}/releases/1/assets{?name,label}`,
|
||||
html_url: `https://${host}/${owner}/${project}/releases/tag/${tag_name}`,
|
||||
tag_name,
|
||||
target_commitish: 'main',
|
||||
name: `Release ${tag_name}`,
|
||||
body: 'Description of the release',
|
||||
draft: false,
|
||||
prerelease: false
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
export const interceptCreate = (
|
||||
server,
|
||||
{
|
||||
api = 'https://api.github.com',
|
||||
host = 'github.com',
|
||||
owner = 'user',
|
||||
project = 'repo',
|
||||
body: {
|
||||
tag_name,
|
||||
name = '',
|
||||
body = null,
|
||||
prerelease = false,
|
||||
draft = false,
|
||||
generate_release_notes = false,
|
||||
make_latest = 'true',
|
||||
discussion_category_name = undefined
|
||||
}
|
||||
} = {}
|
||||
) => {
|
||||
const id = 1;
|
||||
server.post(
|
||||
{
|
||||
url: `/repos/${owner}/${project}/releases`,
|
||||
body: { tag_name, name, body, prerelease, draft, generate_release_notes, make_latest, discussion_category_name }
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
body: {
|
||||
id,
|
||||
tag_name,
|
||||
name,
|
||||
body,
|
||||
prerelease,
|
||||
draft,
|
||||
generate_release_notes,
|
||||
upload_url: `https://uploads.${host}/repos/${owner}/${project}/releases/${id}/assets{?name,label}`,
|
||||
html_url: `https://${host}/${owner}/${project}/releases/tag/${tag_name}`,
|
||||
discussion_url: discussion_category_name ? `https://${host}/${owner}/${project}/discussions/${id}` : undefined
|
||||
},
|
||||
headers: { location: `${api}/repos/${owner}/${project}/releases/${id}` }
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const interceptUpdate = (
|
||||
server,
|
||||
{
|
||||
host = 'github.com',
|
||||
owner = 'user',
|
||||
project = 'repo',
|
||||
body: {
|
||||
tag_name,
|
||||
name = '',
|
||||
body = null,
|
||||
prerelease = false,
|
||||
draft = false,
|
||||
generate_release_notes = false,
|
||||
make_latest = 'true',
|
||||
discussion_category_name = undefined
|
||||
}
|
||||
} = {}
|
||||
) => {
|
||||
server.patch(
|
||||
{
|
||||
url: `/repos/${owner}/${project}/releases/1`,
|
||||
body: {
|
||||
tag_name,
|
||||
name,
|
||||
body,
|
||||
draft,
|
||||
prerelease,
|
||||
generate_release_notes,
|
||||
make_latest,
|
||||
discussion_category_name
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
body: {
|
||||
id: 1,
|
||||
tag_name,
|
||||
name,
|
||||
body,
|
||||
prerelease,
|
||||
draft,
|
||||
generate_release_notes,
|
||||
upload_url: `https://uploads.${host}/repos/${owner}/${project}/releases/1/assets{?name,label}`,
|
||||
html_url: `https://${host}/${owner}/${project}/releases/tag/${tag_name}`
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const interceptAsset = (
|
||||
server,
|
||||
{ api = 'https://api.github.com', host = 'github.com', owner = 'user', project = 'repo', tagName } = {}
|
||||
) => {
|
||||
server.post(
|
||||
{
|
||||
url: `/repos/${owner}/${project}/releases/1/assets`
|
||||
},
|
||||
request => {
|
||||
const id = 1;
|
||||
const url = new URL(request.url);
|
||||
const name = url.searchParams.get('name');
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
id,
|
||||
url: `${api}/repos/${owner}/${project}/releases/assets/${id}`,
|
||||
name,
|
||||
label: '',
|
||||
state: 'uploaded',
|
||||
size: request.headers['content-length'],
|
||||
browser_download_url: `https://${host}/${owner}/${project}/releases/download/${tagName}/${name}`
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
87
node_modules/release-it/test/stub/gitlab.js
generated
vendored
Normal file
87
node_modules/release-it/test/stub/gitlab.js
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
export const interceptMembers = (server, { owner = 'emma' } = {}) => {
|
||||
server.get(`/projects/john%2Frepo/members/all/1`, { status: 200, username: owner });
|
||||
};
|
||||
|
||||
export const interceptUser = (server, { owner = 'user' } = {}, options = {}) => {
|
||||
server.get({ url: '/user', ...options }, { status: 200, body: { id: 1, username: owner } });
|
||||
};
|
||||
|
||||
export const interceptCollaborator = (
|
||||
server,
|
||||
{ owner = 'user', project = 'repo', group, userId = 1 } = {},
|
||||
options = {}
|
||||
) =>
|
||||
server.get(
|
||||
{
|
||||
url: `/projects/${group ? `${group}%2F` : ''}${owner}%2F${project}/members/all/${userId}`,
|
||||
...options
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
body: { id: userId, username: owner, access_level: 30 }
|
||||
}
|
||||
);
|
||||
|
||||
export const interceptPublish = (server, { owner = 'user', project = 'repo', body } = {}) =>
|
||||
server.post(
|
||||
{ url: `/projects/${owner}%2F${project}/releases`, body },
|
||||
{
|
||||
status: 200,
|
||||
body: {
|
||||
_links: {
|
||||
self: `https://gitlab.com/${owner}/${project}/-/releases/${body?.tag_name ?? '1.0.0'}`
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const interceptMilestones = (server, { owner = 'user', project = 'repo', query = {}, milestones = [] } = {}) =>
|
||||
server.get(
|
||||
{
|
||||
url: `/projects/${owner}%2F${project}/milestones`,
|
||||
query: {
|
||||
include_parent_milestones: 'true',
|
||||
...query
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
body: milestones
|
||||
}
|
||||
);
|
||||
|
||||
export const interceptAsset = (server, { owner = 'user', project = 'repo' } = {}) =>
|
||||
server.post(
|
||||
{
|
||||
url: `/projects/${owner}%2F${project}/uploads`
|
||||
},
|
||||
async request => {
|
||||
const formData = await request.formData();
|
||||
const { name } = formData.get('file');
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
alt: name,
|
||||
url: `/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/${name}`,
|
||||
full_path: `/-/project/1234/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/${name}`,
|
||||
markdown: `[${name}](/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/${name})`,
|
||||
_links: {
|
||||
self: `https://gitlab.com/${owner}/${project}/-/releases/${name}`
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const interceptAssetGeneric = (server, { owner = 'user', project = 'repo' } = {}) =>
|
||||
server.put(
|
||||
{
|
||||
url: `/projects/${owner}%2F${project}/packages/generic/release-it/2.0.1/file-v2.0.1.txt`
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
body: {
|
||||
message: '201 Created'
|
||||
}
|
||||
}
|
||||
);
|
||||
36
node_modules/release-it/test/stub/plugin-context.js
generated
vendored
Normal file
36
node_modules/release-it/test/stub/plugin-context.js
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import Plugin from '../../lib/plugin/Plugin.js';
|
||||
|
||||
class ContextPlugin extends Plugin {
|
||||
init() {
|
||||
const context = this.config.getContext();
|
||||
this.exec(`echo ${context.version.isPreRelease}`);
|
||||
this.exec('echo ${version.isPreRelease}');
|
||||
}
|
||||
beforeBump() {
|
||||
const context = this.config.getContext();
|
||||
this.exec(`echo ${context.name} ${context.repo.owner} ${context.latestVersion} ${context.version}`);
|
||||
this.exec('echo ${name} ${repo.owner} ${latestVersion} ${version}');
|
||||
}
|
||||
bump(version) {
|
||||
const repo = this.config.getContext('repo');
|
||||
this.exec(`echo ${repo.owner} ${repo.project} ${repo.repository} ${version}`);
|
||||
this.exec('echo ${repo.owner} ${repo.project} ${repo.repository} ${version}');
|
||||
}
|
||||
beforeRelease() {
|
||||
const { repo, tagName } = this.config.getContext();
|
||||
this.exec(`echo ${repo.owner} ${repo.project} ${repo.repository} ${tagName}`);
|
||||
this.exec('echo ${repo.owner} ${repo.project} ${repo.repository} ${tagName}');
|
||||
}
|
||||
release() {
|
||||
const { repo, latestVersion, version, tagName } = this.config.getContext();
|
||||
this.exec(`echo ${repo.project} ${latestVersion} ${version} ${tagName}`);
|
||||
this.exec('echo ${repo.project} ${latestVersion} ${version} ${tagName}');
|
||||
}
|
||||
afterRelease() {
|
||||
const { repo, latestVersion, version, tagName } = this.config.getContext();
|
||||
this.exec(`echo ${repo.project} ${latestVersion} ${version} ${tagName}`);
|
||||
this.exec('echo ${repo.project} ${latestVersion} ${version} ${tagName}');
|
||||
}
|
||||
}
|
||||
|
||||
export default ContextPlugin;
|
||||
9
node_modules/release-it/test/stub/plugin-replace.js
generated
vendored
Normal file
9
node_modules/release-it/test/stub/plugin-replace.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import Plugin from '../../lib/plugin/Plugin.js';
|
||||
|
||||
class ReplacePlugin extends Plugin {
|
||||
static disablePlugin() {
|
||||
return ['version', 'git', 'npm'];
|
||||
}
|
||||
}
|
||||
|
||||
export default ReplacePlugin;
|
||||
39
node_modules/release-it/test/stub/plugin.js
generated
vendored
Normal file
39
node_modules/release-it/test/stub/plugin.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import Plugin from '../../lib/plugin/Plugin.js';
|
||||
|
||||
class MyPlugin extends Plugin {
|
||||
init() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:init`);
|
||||
}
|
||||
getName() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:getName`);
|
||||
return 'new-project-name';
|
||||
}
|
||||
getLatestVersion() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:getLatestVersion`);
|
||||
return '1.2.3';
|
||||
}
|
||||
getIncrement() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:getIncrement`);
|
||||
return 'minor';
|
||||
}
|
||||
getIncrementedVersionCI() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:getIncrementedVersionCI`);
|
||||
}
|
||||
beforeBump() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:beforeBump`);
|
||||
}
|
||||
bump(version) {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:bump:${version}`);
|
||||
}
|
||||
beforeRelease() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:beforeRelease`);
|
||||
}
|
||||
release() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:release`);
|
||||
}
|
||||
afterRelease() {
|
||||
this.log.info(`${this.namespace}:${this.getContext('name')}:afterRelease`);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyPlugin;
|
||||
24
node_modules/release-it/test/stub/shell.js
generated
vendored
Normal file
24
node_modules/release-it/test/stub/shell.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import util from 'node:util';
|
||||
import Shell from '../../lib/shell.js';
|
||||
|
||||
const debug = util.debug('release-it:shell-stub');
|
||||
|
||||
class ShellStub extends Shell {
|
||||
exec(command) {
|
||||
if (/^(npm (ping|publish|show)|git fetch)/.test(command)) {
|
||||
debug(command);
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (/^npm whoami/.test(command)) {
|
||||
debug(command);
|
||||
return Promise.resolve('john');
|
||||
}
|
||||
if (/^npm access/.test(command)) {
|
||||
debug(command);
|
||||
return Promise.resolve(JSON.stringify({ john: ['write'] }));
|
||||
}
|
||||
return super.exec(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
export default ShellStub;
|
||||
274
node_modules/release-it/test/tasks.interactive.js
generated
vendored
Normal file
274
node_modules/release-it/test/tasks.interactive.js
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
import path from 'node:path';
|
||||
import { renameSync } from 'node:fs';
|
||||
import childProcess from 'node:child_process';
|
||||
import test, { afterEach, after, before, beforeEach, describe, mock } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import Prompt from '../lib/prompt.js';
|
||||
import Config from '../lib/config.js';
|
||||
import runTasks from '../lib/index.js';
|
||||
import Git from '../lib/plugin/git/Git.js';
|
||||
import { execOpts } from '../lib/util.js';
|
||||
import { mkTmpDir, gitAdd, getArgs } from './util/helpers.js';
|
||||
import ShellStub from './stub/shell.js';
|
||||
import { interceptPublish as interceptGitLabPublish } from './stub/gitlab.js';
|
||||
import { interceptCreate as interceptGitHubCreate } from './stub/github.js';
|
||||
import { factory, LogStub, SpinnerStub } from './util/index.js';
|
||||
import { mockFetch } from './util/mock.js';
|
||||
import { createTarBlobByRawContents } from './util/fetch.js';
|
||||
|
||||
describe('tasks.interactive', () => {
|
||||
const [mocker, github, gitlab] = mockFetch(['https://api.github.com', 'https://gitlab.com/api/v4']);
|
||||
|
||||
before(() => {
|
||||
mocker.mockGlobal();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mocker.clearAll();
|
||||
prompt.mock.resetCalls();
|
||||
log.resetCalls();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
mocker.unmockGlobal();
|
||||
});
|
||||
|
||||
const testConfig = {
|
||||
ci: false,
|
||||
config: false
|
||||
};
|
||||
|
||||
const getHooks = plugins => {
|
||||
const hooks = {};
|
||||
['before', 'after'].forEach(prefix => {
|
||||
plugins.forEach(ns => {
|
||||
['init', 'beforeBump', 'bump', 'beforeRelease', 'release', 'afterRelease'].forEach(lifecycle => {
|
||||
hooks[`${prefix}:${lifecycle}`] = `echo ${prefix}:${lifecycle}`;
|
||||
hooks[`${prefix}:${ns}:${lifecycle}`] = `echo ${prefix}:${ns}:${lifecycle}`;
|
||||
});
|
||||
});
|
||||
});
|
||||
return hooks;
|
||||
};
|
||||
|
||||
const log = new LogStub();
|
||||
const spinner = new SpinnerStub();
|
||||
|
||||
const prompt = mock.fn(([options]) => {
|
||||
const answer = options.type === 'list' ? options.choices[0].value : options.name === 'version' ? '0.0.1' : true;
|
||||
return { [options.name]: answer };
|
||||
});
|
||||
|
||||
const defaultInquirer = { prompt };
|
||||
|
||||
const getContainer = (options, inquirer = defaultInquirer) => {
|
||||
const config = new Config(Object.assign({}, testConfig, options));
|
||||
const shell = new ShellStub({ container: { log, config } });
|
||||
const prompt = new Prompt({ container: { inquirer } });
|
||||
return { log, spinner, config, shell, prompt };
|
||||
};
|
||||
|
||||
let bare;
|
||||
let target;
|
||||
beforeEach(async () => {
|
||||
bare = mkTmpDir();
|
||||
target = mkTmpDir();
|
||||
process.chdir(bare);
|
||||
childProcess.execSync(`git init --bare .`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} ${target}`, execOpts);
|
||||
process.chdir(target);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
});
|
||||
|
||||
test('should run tasks without throwing errors', async () => {
|
||||
renameSync('.git', 'foo');
|
||||
const { name, latestVersion, version } = await runTasks({}, getContainer());
|
||||
assert.equal(version, '0.0.1');
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (currently at ${latestVersion})`));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should run tasks using extended configuration', async t => {
|
||||
renameSync('.git', 'foo');
|
||||
|
||||
const validationExtendedConfiguration = "echo 'extended_configuration'";
|
||||
|
||||
github.head('/repos/release-it/release-it-configuration/tarball/main', {
|
||||
status: 200,
|
||||
headers: {}
|
||||
});
|
||||
|
||||
github.get('/repos/release-it/release-it-configuration/tarball/main', {
|
||||
status: 200,
|
||||
body: await new Response(
|
||||
createTarBlobByRawContents({
|
||||
'.release-it.json': JSON.stringify({
|
||||
hooks: {
|
||||
'before:init': validationExtendedConfiguration
|
||||
}
|
||||
})
|
||||
})
|
||||
).arrayBuffer()
|
||||
});
|
||||
|
||||
const config = {
|
||||
$schema: 'https://unpkg.com/release-it@19/schema/release-it.json',
|
||||
extends: 'github:release-it/release-it-configuration',
|
||||
config: true
|
||||
};
|
||||
|
||||
const container = getContainer(config);
|
||||
|
||||
const exec = t.mock.method(container.shell, 'execFormattedCommand');
|
||||
|
||||
const { name, latestVersion, version } = await runTasks({}, container);
|
||||
|
||||
const commands = getArgs(exec, 'echo');
|
||||
|
||||
assert(commands.includes(validationExtendedConfiguration));
|
||||
|
||||
assert.equal(version, '0.0.1');
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (currently at ${latestVersion})`));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should run tasks not using extended configuration as it is not a string', async () => {
|
||||
renameSync('.git', 'foo');
|
||||
|
||||
const config = {
|
||||
$schema: 'https://unpkg.com/release-it@19/schema/release-it.json',
|
||||
extends: false
|
||||
};
|
||||
|
||||
const container = getContainer(config);
|
||||
|
||||
const { name, latestVersion, version } = await runTasks({}, container);
|
||||
|
||||
assert.equal(version, '0.0.1');
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (currently at ${latestVersion})`));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should not run hooks for disabled release-cycle methods', async t => {
|
||||
const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
|
||||
|
||||
const container = getContainer({
|
||||
hooks,
|
||||
git: { push: false },
|
||||
github: { release: false },
|
||||
gitlab: { release: false },
|
||||
npm: { publish: false }
|
||||
});
|
||||
|
||||
const exec = t.mock.method(container.shell, 'execFormattedCommand');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const commands = getArgs(exec, 'echo');
|
||||
|
||||
assert(commands.includes('echo before:init'));
|
||||
assert(commands.includes('echo after:afterRelease'));
|
||||
|
||||
assert(!commands.includes('echo after:git:release'));
|
||||
assert(!commands.includes('echo after:github:release'));
|
||||
assert(!commands.includes('echo after:gitlab:release'));
|
||||
assert(!commands.includes('echo after:npm:release'));
|
||||
});
|
||||
|
||||
test('should not run hooks for cancelled release-cycle methods', async t => {
|
||||
const pkgName = path.basename(target);
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
|
||||
const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
|
||||
const prompt = mock.fn(([options]) => ({ [options.name]: false }));
|
||||
const inquirer = { prompt };
|
||||
|
||||
const container = getContainer(
|
||||
{
|
||||
increment: 'minor',
|
||||
hooks,
|
||||
github: { release: true, skipChecks: true },
|
||||
gitlab: { release: true, skipChecks: true },
|
||||
npm: { publish: true, skipChecks: true }
|
||||
},
|
||||
inquirer
|
||||
);
|
||||
|
||||
const exec = t.mock.method(container.shell, 'execFormattedCommand');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const commands = getArgs(exec, 'echo');
|
||||
|
||||
assert(commands.includes('echo before:init'));
|
||||
assert(commands.includes('echo after:afterRelease'));
|
||||
assert(commands.includes('echo after:git:bump'));
|
||||
assert(commands.includes('echo after:npm:bump'));
|
||||
|
||||
assert(!commands.includes('echo after:git:release'));
|
||||
assert(!commands.includes('echo after:github:release'));
|
||||
assert(!commands.includes('echo after:gitlab:release'));
|
||||
assert(!commands.includes('echo after:npm:release'));
|
||||
});
|
||||
|
||||
test('should run "after:*:release" plugin hooks', async t => {
|
||||
const project = path.basename(bare);
|
||||
const pkgName = path.basename(target);
|
||||
const owner = path.basename(path.dirname(bare));
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
const sha = gitAdd('line', 'file', 'More file');
|
||||
|
||||
const git = await factory(Git);
|
||||
const ref = (await git.getBranchName()) ?? 'HEAD';
|
||||
|
||||
interceptGitHubCreate(github, {
|
||||
owner,
|
||||
project,
|
||||
body: { tag_name: '1.1.0', name: 'Release 1.1.0', body: `* More file (${sha})` }
|
||||
});
|
||||
|
||||
interceptGitLabPublish(gitlab, {
|
||||
owner,
|
||||
project,
|
||||
body: {
|
||||
name: 'Release 1.1.0',
|
||||
ref,
|
||||
tag_name: '1.1.0',
|
||||
tag_message: 'Release 1.1.0',
|
||||
description: `* More file (${sha})`
|
||||
}
|
||||
});
|
||||
|
||||
const hooks = getHooks(['version', 'git', 'github', 'gitlab', 'npm']);
|
||||
|
||||
const container = getContainer({
|
||||
increment: 'minor',
|
||||
hooks,
|
||||
github: { release: true, pushRepo: `https://github.com/${owner}/${project}`, skipChecks: true },
|
||||
gitlab: { release: true, pushRepo: `https://gitlab.com/${owner}/${project}`, skipChecks: true },
|
||||
npm: { name: pkgName, skipChecks: true }
|
||||
});
|
||||
|
||||
const exec = t.mock.method(container.shell, 'execFormattedCommand');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const commands = getArgs(exec, 'echo');
|
||||
|
||||
assert(commands.includes('echo after:git:bump'));
|
||||
assert(commands.includes('echo after:npm:bump'));
|
||||
assert(commands.includes('echo after:git:release'));
|
||||
assert(commands.includes('echo after:github:release'));
|
||||
assert(commands.includes('echo after:gitlab:release'));
|
||||
assert(commands.includes('echo after:npm:release'));
|
||||
});
|
||||
|
||||
test('should show only version prompt', async () => {
|
||||
const config = { ci: false, 'only-version': true };
|
||||
await runTasks({}, getContainer(config));
|
||||
assert.equal(prompt.mock.callCount(), 1);
|
||||
assert.equal(prompt.mock.calls[0].arguments[0][0].name, 'incrementList');
|
||||
});
|
||||
});
|
||||
578
node_modules/release-it/test/tasks.js
generated
vendored
Normal file
578
node_modules/release-it/test/tasks.js
generated
vendored
Normal file
@@ -0,0 +1,578 @@
|
||||
import path from 'node:path';
|
||||
import childProcess from 'node:child_process';
|
||||
import { appendFileSync, mkdirSync, renameSync } from 'node:fs';
|
||||
import test, { after, afterEach, before, beforeEach, describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import semver from 'semver';
|
||||
import Config from '../lib/config.js';
|
||||
import runTasks from '../lib/index.js';
|
||||
import Git from '../lib/plugin/git/Git.js';
|
||||
import { execOpts } from '../lib/util.js';
|
||||
import { mkTmpDir, gitAdd, getArgs } from './util/helpers.js';
|
||||
import ShellStub from './stub/shell.js';
|
||||
import {
|
||||
interceptUser as interceptGitLabUser,
|
||||
interceptCollaborator as interceptGitLabCollaborator,
|
||||
interceptPublish as interceptGitLabPublish,
|
||||
interceptAsset as interceptGitLabAsset
|
||||
} from './stub/gitlab.js';
|
||||
import {
|
||||
interceptAuthentication as interceptGitHubAuthentication,
|
||||
interceptCollaborator as interceptGitHubCollaborator,
|
||||
interceptCreate as interceptGitHubCreate,
|
||||
interceptAsset as interceptGitHubAsset
|
||||
} from './stub/github.js';
|
||||
import { factory, LogStub, SpinnerStub } from './util/index.js';
|
||||
import { mockFetch } from './util/mock.js';
|
||||
|
||||
describe('tasks', () => {
|
||||
const rootDir = new URL('..', import.meta.url);
|
||||
|
||||
const [mocker, github, assets, gitlab] = mockFetch([
|
||||
'https://api.github.com',
|
||||
'https://uploads.github.com',
|
||||
'https://gitlab.com/api/v4'
|
||||
]);
|
||||
|
||||
const npmMajorVersion = semver.major(process.env.npm_config_user_agent.match(/npm\/([^ ]+)/)[1]);
|
||||
|
||||
const testConfig = {
|
||||
ci: true,
|
||||
config: false
|
||||
};
|
||||
|
||||
const log = new LogStub();
|
||||
const spinner = new SpinnerStub();
|
||||
|
||||
const getContainer = options => {
|
||||
const config = new Config(Object.assign({}, testConfig, options));
|
||||
const shell = new ShellStub({ container: { log, config } });
|
||||
return { log, spinner, config, shell };
|
||||
};
|
||||
|
||||
before(() => {
|
||||
mocker.mockGlobal();
|
||||
});
|
||||
|
||||
let bare;
|
||||
let target;
|
||||
beforeEach(async () => {
|
||||
bare = mkTmpDir();
|
||||
target = mkTmpDir();
|
||||
process.chdir(bare);
|
||||
childProcess.execSync(`git init --bare .`, execOpts);
|
||||
childProcess.execSync(`git clone ${bare} ${target}`, execOpts);
|
||||
process.chdir(target);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mocker.clearAll();
|
||||
log.resetCalls();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
mocker.unmockGlobal();
|
||||
});
|
||||
|
||||
test('should run tasks without throwing errors', async () => {
|
||||
renameSync('.git', 'foo');
|
||||
const { name, latestVersion, version } = await runTasks({}, getContainer());
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (${latestVersion}...${version})`));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should run tasks without package.json', async () => {
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
const { name } = await runTasks({}, getContainer({ increment: 'major', git: { commit: false } }));
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes(`release ${name} (1.0.0...2.0.0)`));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
assert.equal(log.warn.mock.callCount(), 0);
|
||||
const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', {
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
assert.equal(stdout.trim(), '2.0.0');
|
||||
});
|
||||
|
||||
test('should disable plugins', async () => {
|
||||
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag 1.2.3', execOpts);
|
||||
gitAdd('line', 'file', 'Add file');
|
||||
const container = getContainer({ increment: 'minor', git: false, npm: false });
|
||||
const { latestVersion, version } = await runTasks({}, container);
|
||||
assert.equal(latestVersion, '0.0.0');
|
||||
assert.equal(version, '0.1.0');
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should run tasks with minimal config and without any warnings/errors', async () => {
|
||||
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag 1.2.3', execOpts);
|
||||
gitAdd('line', 'file', 'More file');
|
||||
await runTasks({}, getContainer({ increment: 'patch' }));
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes('release my-package (1.2.3...1.2.4)'));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
|
||||
assert.equal(stdout.trim(), '1.2.4');
|
||||
});
|
||||
|
||||
test('should use pkg.version', async () => {
|
||||
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
||||
await runTasks({}, getContainer({ increment: 'minor' }));
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes('release my-package (1.2.3...1.3.0)'));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
|
||||
assert.equal(stdout.trim(), '1.3.0');
|
||||
});
|
||||
|
||||
test('should use pkg.version (in sub dir) w/o tagging repo', async t => {
|
||||
gitAdd('{"name":"root-package","version":"1.0.0"}', 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
mkdirSync('my-package');
|
||||
process.chdir('my-package');
|
||||
gitAdd('{"name":"my-package","version":"1.2.3"}', 'package.json', 'Add package.json');
|
||||
const container = getContainer({ increment: 'minor', git: { tag: false } });
|
||||
const exec = t.mock.method(container.shell, 'exec');
|
||||
await runTasks({}, container);
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].endsWith('release my-package (1.2.3...1.3.0)'));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
|
||||
assert.equal(stdout.trim(), '1.0.0');
|
||||
const npmArgs = getArgs(exec, 'npm');
|
||||
assert.equal(npmArgs[5], 'npm version 1.3.0 --no-git-tag-version');
|
||||
});
|
||||
|
||||
test('should ignore version in pkg.version and use git tag instead', async () => {
|
||||
gitAdd('{"name":"my-package","version":"0.0.0"}', 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag 1.1.1', execOpts);
|
||||
gitAdd('line', 'file', 'More file');
|
||||
await runTasks({}, getContainer({ increment: 'minor', npm: { ignoreVersion: true } }));
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].includes('release my-package (1.1.1...1.2.0)'));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
|
||||
assert.equal(stdout.trim(), '1.2.0');
|
||||
});
|
||||
|
||||
test('should release all the things (basic)', async t => {
|
||||
const project = path.basename(bare);
|
||||
const pkgName = path.basename(target);
|
||||
const owner = path.basename(path.dirname(bare));
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag 1.0.0', execOpts);
|
||||
const sha = gitAdd('line', 'file', 'More file');
|
||||
|
||||
interceptGitHubAuthentication(github);
|
||||
interceptGitHubCollaborator(github, { owner, project });
|
||||
interceptGitHubCreate(github, {
|
||||
owner,
|
||||
project,
|
||||
body: { tag_name: '1.0.1', name: 'Release 1.0.1', body: `* More file (${sha})`, prerelease: false }
|
||||
});
|
||||
|
||||
const container = getContainer({
|
||||
github: { release: true, pushRepo: `https://github.com/${owner}/${project}` },
|
||||
npm: { name: pkgName }
|
||||
});
|
||||
|
||||
const exec = t.mock.method(container.shell, 'exec');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const npmArgs = getArgs(exec, 'npm');
|
||||
|
||||
assert.deepEqual(npmArgs, [
|
||||
'npm ping',
|
||||
'npm whoami',
|
||||
`npm show ${pkgName}@latest version`,
|
||||
'npm --version',
|
||||
`npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
|
||||
'npm version 1.0.1 --no-git-tag-version',
|
||||
'npm publish . --tag latest'
|
||||
]);
|
||||
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
|
||||
assert(log.log.mock.calls[0].arguments[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
|
||||
assert(log.log.mock.calls[1].arguments[0].endsWith(`https://github.com/${owner}/${project}/releases/tag/1.0.1`));
|
||||
});
|
||||
|
||||
test('should release with correct tag name', async t => {
|
||||
const project = path.basename(bare);
|
||||
const pkgName = path.basename(target);
|
||||
const owner = path.basename(path.dirname(bare));
|
||||
const stdout = childProcess.execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' });
|
||||
const branchName = stdout.trim();
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
||||
childProcess.execSync(`git tag ${pkgName}-${branchName}-1.0.0`, execOpts);
|
||||
const sha = gitAdd('line', 'file', 'More file');
|
||||
|
||||
interceptGitHubCreate(github, {
|
||||
owner,
|
||||
project,
|
||||
body: {
|
||||
tag_name: `${pkgName}-${branchName}-1.0.1`,
|
||||
name: 'Release 1.0.1',
|
||||
body: `* More file (${sha})`,
|
||||
prerelease: false
|
||||
}
|
||||
});
|
||||
|
||||
const container = getContainer({
|
||||
git: { tagName: '${npm.name}-${branchName}-${version}' },
|
||||
github: { release: true, skipChecks: true, pushRepo: `https://github.com/${owner}/${project}` }
|
||||
});
|
||||
|
||||
const exec = t.mock.method(container.shell, 'exec');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const gitArgs = getArgs(exec, 'git');
|
||||
|
||||
assert(gitArgs.includes(`git tag --annotate --message Release 1.0.1 ${pkgName}-${branchName}-1.0.1`));
|
||||
assert(
|
||||
log.log.mock.calls[1].arguments[0].endsWith(`/${owner}/${project}/releases/tag/${pkgName}-${branchName}-1.0.1`)
|
||||
);
|
||||
});
|
||||
|
||||
test('should release all the things (pre-release, github, gitlab)', async t => {
|
||||
const project = path.basename(bare);
|
||||
const pkgName = path.basename(target);
|
||||
const owner = path.basename(path.dirname(bare));
|
||||
const url = `https://gitlab.com/${owner}/${project}`;
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag v1.0.0', execOpts);
|
||||
const sha = gitAdd('line', 'file', 'More file');
|
||||
childProcess.execSync('git push --follow-tags', execOpts);
|
||||
const git = await factory(Git);
|
||||
const ref = (await git.getBranchName()) ?? 'HEAD';
|
||||
|
||||
interceptGitHubAuthentication(github);
|
||||
interceptGitHubCollaborator(github, { owner, project });
|
||||
interceptGitHubAsset(assets, { owner, project, body: 'lineline' });
|
||||
interceptGitHubCreate(github, {
|
||||
owner,
|
||||
project,
|
||||
body: {
|
||||
tag_name: 'v1.1.0-alpha.0',
|
||||
name: 'Release 1.1.0-alpha.0',
|
||||
body: `Notes for ${pkgName} [v1.1.0-alpha.0]: ${sha}`,
|
||||
prerelease: true
|
||||
}
|
||||
});
|
||||
|
||||
interceptGitLabUser(gitlab, { owner });
|
||||
interceptGitLabCollaborator(gitlab, { owner, project });
|
||||
interceptGitLabAsset(gitlab, { owner, project });
|
||||
interceptGitLabPublish(gitlab, {
|
||||
owner,
|
||||
project,
|
||||
body: {
|
||||
name: 'Release 1.1.0-alpha.0',
|
||||
ref,
|
||||
tag_name: 'v1.1.0-alpha.0',
|
||||
tag_message: `${owner} ${owner}/${project} ${project}`,
|
||||
description: `Notes for ${pkgName}: ${sha}`,
|
||||
assets: {
|
||||
links: [
|
||||
{
|
||||
name: 'file',
|
||||
url: `${url}/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file`
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const container = getContainer({
|
||||
increment: 'minor',
|
||||
preRelease: 'alpha',
|
||||
git: {
|
||||
changelog: 'git log --pretty=format:%h ${latestTag}...HEAD',
|
||||
commitMessage: 'Release ${version} for ${name} (from ${latestVersion})',
|
||||
tagAnnotation: '${repo.owner} ${repo.repository} ${repo.project}'
|
||||
},
|
||||
github: {
|
||||
release: true,
|
||||
pushRepo: `https://github.com/${owner}/${project}`,
|
||||
releaseNotes: 'echo Notes for ${name} [v${version}]: ${changelog}',
|
||||
assets: ['file']
|
||||
},
|
||||
gitlab: {
|
||||
release: true,
|
||||
pushRepo: url,
|
||||
releaseNotes: 'echo Notes for ${name}: ${changelog}',
|
||||
assets: ['file']
|
||||
},
|
||||
npm: { name: pkgName }
|
||||
});
|
||||
|
||||
const exec = t.mock.method(container.shell, 'exec');
|
||||
|
||||
process.env['GITLAB_TOKEN'] = '123';
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const npmArgs = getArgs(exec, 'npm');
|
||||
|
||||
assert.deepEqual(npmArgs, [
|
||||
'npm ping',
|
||||
'npm whoami',
|
||||
`npm show ${pkgName}@latest version`,
|
||||
'npm --version',
|
||||
`npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
|
||||
'npm version 1.1.0-alpha.0 --no-git-tag-version',
|
||||
'npm publish . --tag alpha'
|
||||
]);
|
||||
|
||||
const commitMessage = childProcess.execSync('git log --oneline --format=%B -n 1 HEAD', {
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
assert.equal(commitMessage.trim(), `Release 1.1.0-alpha.0 for ${pkgName} (from 1.0.0)`);
|
||||
|
||||
const tagName = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
|
||||
assert.equal(tagName.trim(), 'v1.1.0-alpha.0');
|
||||
|
||||
const tagAnnotation = childProcess.execSync('git for-each-ref refs/tags/v1.1.0-alpha.0 --format="%(contents)"', {
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
assert.equal(tagAnnotation.trim(), `${owner} ${owner}/${project} ${project}`);
|
||||
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...1.1.0-alpha.0)`));
|
||||
assert(log.log.mock.calls[0].arguments[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
|
||||
assert(log.log.mock.calls[1].arguments[0].endsWith(`/${owner}/${project}/releases/tag/v1.1.0-alpha.0`));
|
||||
assert(log.log.mock.calls[2].arguments[0].endsWith(`/${project}/-/releases/v1.1.0-alpha.0`));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should publish pre-release without pre-id with different npm.tag', async t => {
|
||||
const pkgName = path.basename(target);
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
||||
childProcess.execSync('git tag v1.0.0', execOpts);
|
||||
|
||||
const container = getContainer({ increment: 'major', preRelease: true, npm: { name: pkgName, tag: 'next' } });
|
||||
const exec = t.mock.method(container.shell, 'exec');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const npmArgs = getArgs(exec, 'npm');
|
||||
assert.deepEqual(npmArgs, [
|
||||
'npm ping',
|
||||
'npm whoami',
|
||||
`npm show ${pkgName}@latest version`,
|
||||
'npm --version',
|
||||
`npm access ${npmMajorVersion >= 9 ? 'list collaborators --json' : 'ls-collaborators'} ${pkgName}`,
|
||||
'npm version 2.0.0-0 --no-git-tag-version',
|
||||
'npm publish . --tag next'
|
||||
]);
|
||||
|
||||
const stdout = childProcess.execSync('git describe --tags --match=* --abbrev=0', { encoding: 'utf-8' });
|
||||
assert.equal(stdout.trim(), 'v2.0.0-0');
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...2.0.0-0)`));
|
||||
assert(log.log.mock.calls[0].arguments[0].endsWith(`https://www.npmjs.com/package/${pkgName}`));
|
||||
assert.match(log.log.mock.calls.at(-1).arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should handle private package correctly, bump lockfile', async t => {
|
||||
const pkgName = path.basename(target);
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package.json', 'Add package.json');
|
||||
gitAdd(`{"name":"${pkgName}","version":"1.0.0","private":true}`, 'package-lock.json', 'Add package-lock.json');
|
||||
|
||||
const container = getContainer({ npm: { name: pkgName, private: true } });
|
||||
const exec = t.mock.method(container.shell, 'exec');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const npmArgs = getArgs(exec, 'npm');
|
||||
assert.deepEqual(npmArgs, ['npm version 1.0.1 --no-git-tag-version']);
|
||||
assert(log.obtrusive.mock.calls[0].arguments[0].endsWith(`release ${pkgName} (1.0.0...1.0.1)`));
|
||||
assert.equal(log.warn.length, 0);
|
||||
assert.match(log.log.mock.calls[0].arguments[0], /Done \(in [0-9]+s\.\)/);
|
||||
});
|
||||
|
||||
test('should initially publish non-private scoped npm package privately', async t => {
|
||||
const pkgName = path.basename(target);
|
||||
gitAdd(`{"name":"@scope/${pkgName}","version":"1.0.0"}`, 'package.json', 'Add package.json');
|
||||
|
||||
const container = getContainer({ npm: { name: pkgName } });
|
||||
|
||||
const original = container.shell.exec.bind(container.shell);
|
||||
const exec = t.mock.method(container.shell, 'exec', (...args) => {
|
||||
if (args[0] === `npm show @scope/${pkgName}@latest version`) return Promise.reject();
|
||||
return original(...args);
|
||||
});
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const npmArgs = getArgs(exec, 'npm');
|
||||
assert.equal(npmArgs[6], 'npm publish . --tag latest');
|
||||
});
|
||||
|
||||
test('should use pkg.publishConfig.registry', async t => {
|
||||
const pkgName = path.basename(target);
|
||||
const registry = 'https://my-registry.example.org';
|
||||
|
||||
gitAdd(
|
||||
JSON.stringify({
|
||||
name: pkgName,
|
||||
version: '1.2.3',
|
||||
publishConfig: { registry }
|
||||
}),
|
||||
'package.json',
|
||||
'Add package.json'
|
||||
);
|
||||
|
||||
const container = getContainer();
|
||||
|
||||
const exec = t.mock.method(container.shell, 'exec');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const npmArgs = getArgs(exec, 'npm');
|
||||
assert.equal(npmArgs[0], `npm ping --registry ${registry}`);
|
||||
assert.equal(npmArgs[1], `npm whoami --registry ${registry}`);
|
||||
assert(container.log.log.mock.calls[0].arguments[0].endsWith(`${registry}/package/${pkgName}`));
|
||||
});
|
||||
|
||||
test('should propagate errors', async () => {
|
||||
const config = {
|
||||
hooks: {
|
||||
'before:init': 'some-failing-command'
|
||||
}
|
||||
};
|
||||
const container = getContainer(config);
|
||||
|
||||
await assert.rejects(runTasks({}, container), { message: /some-failing-command/ });
|
||||
|
||||
assert.equal(log.error.mock.callCount(), 1);
|
||||
});
|
||||
|
||||
test('should use custom changelog command with context', async t => {
|
||||
const project = path.basename(bare);
|
||||
const owner = path.basename(path.dirname(bare));
|
||||
childProcess.execSync('git tag v1.0.0', execOpts);
|
||||
gitAdd('line', 'file', 'More file');
|
||||
|
||||
interceptGitHubAuthentication(github);
|
||||
interceptGitHubCollaborator(github, { owner, project });
|
||||
interceptGitHubCreate(github, {
|
||||
owner,
|
||||
project,
|
||||
body: {
|
||||
tag_name: 'v1.1.0',
|
||||
name: 'Release 1.1.0',
|
||||
body: 'custom-changelog-generator --from=v1.0.0 --to=v1.1.0',
|
||||
draft: false,
|
||||
prerelease: false
|
||||
}
|
||||
});
|
||||
|
||||
const container = getContainer({
|
||||
increment: 'minor',
|
||||
github: {
|
||||
release: true,
|
||||
releaseNotes: 'echo custom-changelog-generator --from=${latestTag} --to=${tagName}',
|
||||
pushRepo: `https://github.com/${owner}/${project}`
|
||||
}
|
||||
});
|
||||
|
||||
const exec = t.mock.method(container.shell, 'execStringCommand');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const command = exec.mock.calls
|
||||
.map(call => call.arguments)
|
||||
.find(([command]) => command.includes('custom-changelog-generator'));
|
||||
|
||||
assert.equal(command[0], 'echo custom-changelog-generator --from=v1.0.0 --to=v1.1.0');
|
||||
});
|
||||
|
||||
test('should run all hooks', async t => {
|
||||
gitAdd(`{"name":"hooked","version":"1.0.0","type":"module"}`, 'package.json', 'Add package.json');
|
||||
childProcess.execSync(`npm install ${rootDir}`, execOpts);
|
||||
const plugin = "import { Plugin } from 'release-it'; class MyPlugin extends Plugin {}; export default MyPlugin;";
|
||||
|
||||
appendFileSync('my-plugin.js', plugin);
|
||||
const hooks = {};
|
||||
['before', 'after'].forEach(prefix => {
|
||||
['version', 'git', 'npm', 'my-plugin'].forEach(ns => {
|
||||
['init', 'beforeBump', 'bump', 'beforeRelease', 'release', 'afterRelease'].forEach(cycle => {
|
||||
hooks[`${prefix}:${cycle}`] = `echo ${prefix}:${cycle}`;
|
||||
hooks[`${prefix}:${ns}:${cycle}`] = `echo ${prefix}:${ns}:${cycle}`;
|
||||
});
|
||||
});
|
||||
});
|
||||
const container = getContainer({
|
||||
plugins: { './my-plugin.js': {} },
|
||||
git: { requireCleanWorkingDir: false },
|
||||
hooks
|
||||
});
|
||||
const exec = t.mock.method(container.shell, 'execFormattedCommand');
|
||||
|
||||
await runTasks({}, container);
|
||||
|
||||
const commands = getArgs(exec, 'echo');
|
||||
|
||||
assert.deepEqual(commands, [
|
||||
'echo before:init',
|
||||
'echo before:my-plugin:init',
|
||||
'echo after:my-plugin:init',
|
||||
'echo before:npm:init',
|
||||
'echo after:npm:init',
|
||||
'echo before:git:init',
|
||||
'echo after:git:init',
|
||||
'echo before:version:init',
|
||||
'echo after:version:init',
|
||||
'echo after:init',
|
||||
'echo before:beforeBump',
|
||||
'echo before:my-plugin:beforeBump',
|
||||
'echo after:my-plugin:beforeBump',
|
||||
'echo before:npm:beforeBump',
|
||||
'echo after:npm:beforeBump',
|
||||
'echo before:git:beforeBump',
|
||||
'echo after:git:beforeBump',
|
||||
'echo before:version:beforeBump',
|
||||
'echo after:version:beforeBump',
|
||||
'echo after:beforeBump',
|
||||
'echo before:bump',
|
||||
'echo before:my-plugin:bump',
|
||||
'echo after:my-plugin:bump',
|
||||
'echo before:npm:bump',
|
||||
'echo after:npm:bump',
|
||||
'echo before:git:bump',
|
||||
'echo after:git:bump',
|
||||
'echo before:version:bump',
|
||||
'echo after:version:bump',
|
||||
'echo after:bump',
|
||||
'echo before:beforeRelease',
|
||||
'echo before:my-plugin:beforeRelease',
|
||||
'echo after:my-plugin:beforeRelease',
|
||||
'echo before:npm:beforeRelease',
|
||||
'echo after:npm:beforeRelease',
|
||||
'echo before:git:beforeRelease',
|
||||
'echo after:git:beforeRelease',
|
||||
'echo before:version:beforeRelease',
|
||||
'echo after:version:beforeRelease',
|
||||
'echo after:beforeRelease',
|
||||
'echo before:release',
|
||||
'echo before:npm:release',
|
||||
'echo after:npm:release',
|
||||
'echo before:git:release',
|
||||
'echo after:git:release',
|
||||
'echo before:version:release',
|
||||
'echo after:version:release',
|
||||
'echo before:my-plugin:release',
|
||||
'echo after:my-plugin:release',
|
||||
'echo after:release',
|
||||
'echo before:afterRelease',
|
||||
'echo before:npm:afterRelease',
|
||||
'echo after:npm:afterRelease',
|
||||
'echo before:git:afterRelease',
|
||||
'echo after:git:afterRelease',
|
||||
'echo before:version:afterRelease',
|
||||
'echo after:version:afterRelease',
|
||||
'echo before:my-plugin:afterRelease',
|
||||
'echo after:my-plugin:afterRelease',
|
||||
'echo after:afterRelease'
|
||||
]);
|
||||
});
|
||||
});
|
||||
32
node_modules/release-it/test/util/fetch.js
generated
vendored
Normal file
32
node_modules/release-it/test/util/fetch.js
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Readable } from 'node:stream';
|
||||
import { create as createTar } from 'tar';
|
||||
import { appendFile, mkTmpDir } from './helpers.js';
|
||||
|
||||
export function createTarBlob(dir) {
|
||||
const stream = new Readable({
|
||||
read() {}
|
||||
});
|
||||
|
||||
createTar(
|
||||
{
|
||||
gzip: true,
|
||||
portable: true,
|
||||
sync: false,
|
||||
cwd: dir
|
||||
},
|
||||
['.']
|
||||
)
|
||||
.on('data', chunk => stream.push(chunk))
|
||||
.on('end', () => stream.push(null));
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
export function createTarBlobByRawContents(contents) {
|
||||
const dir = mkTmpDir();
|
||||
for (const [key, value] of Object.entries(contents)) {
|
||||
appendFile(value, key, dir);
|
||||
}
|
||||
|
||||
return createTarBlob(dir);
|
||||
}
|
||||
40
node_modules/release-it/test/util/helpers.js
generated
vendored
Normal file
40
node_modules/release-it/test/util/helpers.js
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { appendFileSync, mkdirSync, mkdtempSync, promises } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import childProcess from 'node:child_process';
|
||||
import { execOpts } from '../../lib/util.js';
|
||||
|
||||
const mkTmpDir = () => {
|
||||
const dir = mkdtempSync(path.join(os.tmpdir(), 'release-it-'));
|
||||
return dir;
|
||||
};
|
||||
|
||||
const readFile = file => promises.readFile(path.resolve(file), 'utf8');
|
||||
|
||||
const gitAdd = (content, filePath, message) => {
|
||||
appendFile(content, filePath);
|
||||
childProcess.execSync(`git add ${filePath}`, execOpts);
|
||||
const stdout = childProcess.execSync(`git commit -m "${message}"`, { encoding: 'utf-8' });
|
||||
const match = stdout.match(/\[.+([a-z0-9]{7})\]/);
|
||||
return match ? match[1] : null;
|
||||
};
|
||||
|
||||
const getArgs = (fn, prefix) =>
|
||||
fn.mock.calls
|
||||
.map(call => call.arguments[0])
|
||||
.map(arg => (typeof arg !== 'string' ? arg.join(' ') : arg))
|
||||
.filter(cmd => cmd.startsWith(prefix))
|
||||
.map(cmd => cmd.trim());
|
||||
|
||||
const appendFile = (content, filePath, cwd) => {
|
||||
filePath = path.resolve(cwd ?? '', filePath);
|
||||
const dirPath = path.dirname(filePath);
|
||||
|
||||
if (dirPath) {
|
||||
mkdirSync(dirPath, { mode: parseInt('0777', 8), recursive: true });
|
||||
}
|
||||
|
||||
appendFileSync(filePath, content);
|
||||
};
|
||||
|
||||
export { mkTmpDir, readFile, gitAdd, getArgs, appendFile };
|
||||
22
node_modules/release-it/test/util/https-server/client/my-private-root-ca.cert.pem
generated
vendored
Normal file
22
node_modules/release-it/test/util/https-server/client/my-private-root-ca.cert.pem
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrzCCApegAwIBAgIUUAyKcu86WuJQd1fdlHsXkCl1mYMwDQYJKoZIhvcNAQEL
|
||||
BQAwZzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFV0YWgxDjAMBgNVBAcMBVByb3Zv
|
||||
MSMwIQYDVQQKDBpBQ01FIFNpZ25pbmcgQXV0aG9yaXR5IEluYzEUMBIGA1UEAwwL
|
||||
ZXhhbXBsZS5jb20wHhcNMjQxMjExMTMxMzIyWhcNMjcxMDAxMTMxMzIyWjBnMQsw
|
||||
CQYDVQQGEwJVUzENMAsGA1UECAwEVXRhaDEOMAwGA1UEBwwFUHJvdm8xIzAhBgNV
|
||||
BAoMGkFDTUUgU2lnbmluZyBBdXRob3JpdHkgSW5jMRQwEgYDVQQDDAtleGFtcGxl
|
||||
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKlp3Kz278oyTDY4
|
||||
+1iNv1xMzWCJBWf7YxyYkXACVvZzUqgwxotNwAE/kE131HgpBJnu7HsDdS8nLy80
|
||||
mDkFCGmqEuVZMDaMGtcz3HJSklESjxYM1VNgifz2IlNmoaidNehFkCi1rz6cLMR0
|
||||
rEZpi98EbVZGl3n0vDD0eEZF5QQn/eDwclt/aKVBVLcQxo6MHhQdaK85a1uNyUU3
|
||||
HPUYiyPGVn9oX9PGcOpYFomJoBdg3sUVk32xKCUf15+uqVchdtyGFW/bp5KSBCnB
|
||||
QD0TzN+SgBHnVTzIfoi8WY56uuwnYauvT4fRh8SfkV91BzHBtWSbulNTfTUzgWhs
|
||||
VvCGw7ECAwEAAaNTMFEwHQYDVR0OBBYEFC4qTshaPoBie1ukhhM0KBi95NzJMB8G
|
||||
A1UdIwQYMBaAFC4qTshaPoBie1ukhhM0KBi95NzJMA8GA1UdEwEB/wQFMAMBAf8w
|
||||
DQYJKoZIhvcNAQELBQADggEBAB+fRdUHgqwpTWRfF+jQB4Af75HTfp6hgemUjapI
|
||||
eZn/OugS75/jfJt9npVsHl/aa63GL/W6kQShoMVOhrYqW52J1TSsLKZR2L7sv0ji
|
||||
KYfakv+aLkRKewPoVadsCL8GUmaCByE9mwlhmmZprkjDmA3hWsjEM5lyg7qleJ0k
|
||||
V32FVysdhLLnftt2SJB7lyoTujhkNAjJhLT/0Qr8t59v0sViPtL8532jSXqE1GK+
|
||||
zncsJDK7v2VEuurz1lPTRY6tPQOJ1Qt8vUzDH/ugcc5JPBEuHhjjrd5K65lxnGNw
|
||||
lnPHIS7FJm1OMkuatQXomNuuoWDPyM7fuVyGUUpmlkbpJsg=
|
||||
-----END CERTIFICATE-----
|
||||
BIN
node_modules/release-it/test/util/https-server/client/my-private-root-ca.crt
generated
vendored
Normal file
BIN
node_modules/release-it/test/util/https-server/client/my-private-root-ca.crt
generated
vendored
Normal file
Binary file not shown.
65
node_modules/release-it/test/util/https-server/gen-cert.sh
generated
vendored
Executable file
65
node_modules/release-it/test/util/https-server/gen-cert.sh
generated
vendored
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generate client and server self-signed HTTPS certificates
|
||||
# Adapted from:
|
||||
# https://stackoverflow.com/questions/19665863/how-do-i-use-a-self-signed-certificate-for-a-https-node-js-server/24749608#24749608
|
||||
|
||||
FQDN=$1
|
||||
|
||||
if [ -z "$FQDN" ]; then
|
||||
echo -e "\nError: Missing required parameter for domain name.\n"
|
||||
echo -e "Usage:\n\t./gen-cert.sh <DOMAIN_NAME>\n"
|
||||
exit 1
|
||||
fi;
|
||||
|
||||
# make directories to work from
|
||||
mkdir -p server/ client/ all/
|
||||
|
||||
# Create your very own Root Certificate Authority
|
||||
openssl genrsa \
|
||||
-out all/my-private-root-ca.privkey.pem \
|
||||
2048
|
||||
|
||||
# Self-sign your Root Certificate Authority
|
||||
# Since this is private, the details can be as bogus as you like
|
||||
openssl req \
|
||||
-x509 \
|
||||
-new \
|
||||
-nodes \
|
||||
-key all/my-private-root-ca.privkey.pem \
|
||||
-days 1024 \
|
||||
-out all/my-private-root-ca.cert.pem \
|
||||
-subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.com"
|
||||
|
||||
# Create a Device Certificate for each domain,
|
||||
# such as example.com, *.example.com, awesome.example.com
|
||||
# NOTE: You MUST match CN to the domain name or ip address you want to use
|
||||
openssl genrsa \
|
||||
-out all/privkey.pem \
|
||||
2048
|
||||
|
||||
# Create a request from your Device, which your Root CA will sign
|
||||
openssl req -new \
|
||||
-key all/privkey.pem \
|
||||
-out all/csr.pem \
|
||||
-subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=${FQDN}"
|
||||
|
||||
# Sign the request from Device with your Root CA
|
||||
openssl x509 \
|
||||
-req -in all/csr.pem \
|
||||
-CA all/my-private-root-ca.cert.pem \
|
||||
-CAkey all/my-private-root-ca.privkey.pem \
|
||||
-CAcreateserial \
|
||||
-out all/cert.pem \
|
||||
-days 500
|
||||
|
||||
# Put things in their proper place
|
||||
rsync -a all/{privkey,cert}.pem server/
|
||||
cat all/cert.pem > server/fullchain.pem # we have no intermediates in this case
|
||||
rsync -a all/my-private-root-ca.cert.pem server/
|
||||
rsync -a all/my-private-root-ca.cert.pem client/
|
||||
|
||||
# create DER format crt for iOS Mobile Safari, etc
|
||||
openssl x509 -outform der -in all/my-private-root-ca.cert.pem -out client/my-private-root-ca.crt
|
||||
|
||||
rm -r all
|
||||
150
node_modules/release-it/test/util/https-server/server.js
generated
vendored
Normal file
150
node_modules/release-it/test/util/https-server/server.js
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
import { createServer } from 'node:https';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { debug } from 'node:util';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/**
|
||||
* @typedef {import('http').IncomingMessage} IncomingMessage
|
||||
* @typedef {import('http').ServerResponse} ServerResponse
|
||||
* @typedef {ServerResponse & { req: IncomingMessage;}} RequestResponse
|
||||
* @typedef {import('https').ServerOptions} ServerOptions
|
||||
*/
|
||||
|
||||
const DIRNAME = getDirname();
|
||||
|
||||
/** @type {ServerOptions} */
|
||||
const options = {
|
||||
key: readFileSync(join(DIRNAME, './server/privkey.pem')),
|
||||
cert: readFileSync(join(DIRNAME, './server/fullchain.pem'))
|
||||
};
|
||||
|
||||
/**
|
||||
* Basic https server to use for the Gitlab tests.
|
||||
* Uses a self-signed HTTPS certificate to allow testing gitlab release options
|
||||
* like `insecure` or `certificateAuthorityFile`.
|
||||
*
|
||||
* The certicates were generated using the gen-cert.sh script in this folder
|
||||
* with the following command:
|
||||
*
|
||||
* `./gen-cert.sh localhost`
|
||||
*
|
||||
*/
|
||||
export class GitlabTestServer {
|
||||
constructor() {
|
||||
this.server = createServer(options, (req, res) => this._requestHandler(req, res));
|
||||
this.debug = debug('release-it:gitlab-test-server');
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server with the given port and host
|
||||
*
|
||||
* @param {number} [port]
|
||||
* @param {string} [host]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
run(port = 3000, host) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.server.listening) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.server.listen(port, host, () => {
|
||||
const address = this.server.address();
|
||||
this.debug('Server listening on https://' + address.address + ':' + address.port);
|
||||
resolve();
|
||||
});
|
||||
|
||||
this.server.on('error', e => {
|
||||
if (e.code === 'EADDRINUSE') {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
|
||||
this.debug(e.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the server
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
stop() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.server.listening) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.server.removeAllListeners();
|
||||
|
||||
this.server.close(err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.debug('Server successfully closed.');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Server's main request handler
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {RequestResponse} res
|
||||
* @returns {void}
|
||||
*/
|
||||
_requestHandler(req, res) {
|
||||
if (req.url === '/api/v4/user') {
|
||||
this._json(res, { id: '1234', username: 'release_bot' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url.startsWith('/api/v4/projects') && req.url.endsWith('/members/all/1234')) {
|
||||
this._json(res, { access_level: 50 });
|
||||
return;
|
||||
}
|
||||
|
||||
this._text(res, 'Ok');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Sends out a JSON response
|
||||
*
|
||||
* @param {RequestResponse} res
|
||||
* @param {object} payload
|
||||
*/
|
||||
_json(res, payload) {
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Sends out a text response
|
||||
*
|
||||
* @param {RequestResponse} res
|
||||
* @param {string} message
|
||||
*/
|
||||
_text(res, message) {
|
||||
res.writeHead(200, { 'content-type': 'text/plan' });
|
||||
res.end(message);
|
||||
}
|
||||
}
|
||||
|
||||
function getDirname() {
|
||||
if (import.meta.dirname) return import.meta.dirname;
|
||||
|
||||
return fileURLToPath(new URL('.', import.meta.url));
|
||||
}
|
||||
20
node_modules/release-it/test/util/https-server/server/cert.pem
generated
vendored
Normal file
20
node_modules/release-it/test/util/https-server/server/cert.pem
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDRjCCAi4CFGCNNJXfcHchSO1xHnWt5wcENGznMA0GCSqGSIb3DQEBCwUAMGcx
|
||||
CzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARVdGFoMQ4wDAYDVQQHDAVQcm92bzEjMCEG
|
||||
A1UECgwaQUNNRSBTaWduaW5nIEF1dGhvcml0eSBJbmMxFDASBgNVBAMMC2V4YW1w
|
||||
bGUuY29tMB4XDTI0MTIxMTEzMTMyMloXDTI2MDQyNTEzMTMyMlowWDELMAkGA1UE
|
||||
BhMCVVMxDTALBgNVBAgMBFV0YWgxDjAMBgNVBAcMBVByb3ZvMRYwFAYDVQQKDA1B
|
||||
Q01FIFRlY2ggSW5jMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQC1+BYy7XcJoIAHqPcU7f74Dp/6N6f/hjLTADMIT1OO
|
||||
49W3Rrc6xbJjeLsiTD0Rj9Z/9CSI7Vh/AOLoW7OGMOZkJZNRw8L9lc14ZDB1FAcJ
|
||||
ITn8c9d0d0YltbxUOq509wEP6GSxYCqYZlvAjeyYADRW5PnP82n6MRMW9Ve5y50s
|
||||
ijeaVk120je5zKvQTd0IR9rXf8K3M8CmnlEVuuf4+uXvg0gUsGvSSkBxPVGeUQHM
|
||||
/zRxj/I8WYCewb9Qs2TSJjgnFsfUCF6C3xs/T8p4vJ8PxeLs6GNcQhhtNH0q7Wxa
|
||||
IV4cdrXkxPi6YS8ph1WErqNR2pdsCA5bUNTCT32vPjr/AgMBAAEwDQYJKoZIhvcN
|
||||
AQELBQADggEBACu1hC1yvFKF6nEjz4mdTmVIyqktfkQX4+5edpSCLM0UhOmQ9Tm3
|
||||
3NnQ06YhPnAkiXLiyKxoCGqulgh1B1+3Ii0/ttDq4D4HIEVMwT5Hmko3vppUkJD7
|
||||
aIacmTKxGFrtF+p1hIDXmAAYFUB2bWDgVvba2z70DkbffJBOwe/+2+hOgFXI5x3+
|
||||
IJuF0bYKRlFG0yvVX+ooWAaKmCSR4reCsjWNuP7KBBJv15GZLKfHPjDUuxW3u43/
|
||||
ZmyBM+1Fs91jt0v5w1dIDF0z1pxwbaRJ3w1M1kEU8i6/q+1YaahYvdeaa0jsdSsy
|
||||
kpz3YhZtb324TKmjfUFMzYvfcoR8IYDkekQ=
|
||||
-----END CERTIFICATE-----
|
||||
20
node_modules/release-it/test/util/https-server/server/fullchain.pem
generated
vendored
Normal file
20
node_modules/release-it/test/util/https-server/server/fullchain.pem
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDRjCCAi4CFGCNNJXfcHchSO1xHnWt5wcENGznMA0GCSqGSIb3DQEBCwUAMGcx
|
||||
CzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARVdGFoMQ4wDAYDVQQHDAVQcm92bzEjMCEG
|
||||
A1UECgwaQUNNRSBTaWduaW5nIEF1dGhvcml0eSBJbmMxFDASBgNVBAMMC2V4YW1w
|
||||
bGUuY29tMB4XDTI0MTIxMTEzMTMyMloXDTI2MDQyNTEzMTMyMlowWDELMAkGA1UE
|
||||
BhMCVVMxDTALBgNVBAgMBFV0YWgxDjAMBgNVBAcMBVByb3ZvMRYwFAYDVQQKDA1B
|
||||
Q01FIFRlY2ggSW5jMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQC1+BYy7XcJoIAHqPcU7f74Dp/6N6f/hjLTADMIT1OO
|
||||
49W3Rrc6xbJjeLsiTD0Rj9Z/9CSI7Vh/AOLoW7OGMOZkJZNRw8L9lc14ZDB1FAcJ
|
||||
ITn8c9d0d0YltbxUOq509wEP6GSxYCqYZlvAjeyYADRW5PnP82n6MRMW9Ve5y50s
|
||||
ijeaVk120je5zKvQTd0IR9rXf8K3M8CmnlEVuuf4+uXvg0gUsGvSSkBxPVGeUQHM
|
||||
/zRxj/I8WYCewb9Qs2TSJjgnFsfUCF6C3xs/T8p4vJ8PxeLs6GNcQhhtNH0q7Wxa
|
||||
IV4cdrXkxPi6YS8ph1WErqNR2pdsCA5bUNTCT32vPjr/AgMBAAEwDQYJKoZIhvcN
|
||||
AQELBQADggEBACu1hC1yvFKF6nEjz4mdTmVIyqktfkQX4+5edpSCLM0UhOmQ9Tm3
|
||||
3NnQ06YhPnAkiXLiyKxoCGqulgh1B1+3Ii0/ttDq4D4HIEVMwT5Hmko3vppUkJD7
|
||||
aIacmTKxGFrtF+p1hIDXmAAYFUB2bWDgVvba2z70DkbffJBOwe/+2+hOgFXI5x3+
|
||||
IJuF0bYKRlFG0yvVX+ooWAaKmCSR4reCsjWNuP7KBBJv15GZLKfHPjDUuxW3u43/
|
||||
ZmyBM+1Fs91jt0v5w1dIDF0z1pxwbaRJ3w1M1kEU8i6/q+1YaahYvdeaa0jsdSsy
|
||||
kpz3YhZtb324TKmjfUFMzYvfcoR8IYDkekQ=
|
||||
-----END CERTIFICATE-----
|
||||
22
node_modules/release-it/test/util/https-server/server/my-private-root-ca.cert.pem
generated
vendored
Normal file
22
node_modules/release-it/test/util/https-server/server/my-private-root-ca.cert.pem
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrzCCApegAwIBAgIUUAyKcu86WuJQd1fdlHsXkCl1mYMwDQYJKoZIhvcNAQEL
|
||||
BQAwZzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFV0YWgxDjAMBgNVBAcMBVByb3Zv
|
||||
MSMwIQYDVQQKDBpBQ01FIFNpZ25pbmcgQXV0aG9yaXR5IEluYzEUMBIGA1UEAwwL
|
||||
ZXhhbXBsZS5jb20wHhcNMjQxMjExMTMxMzIyWhcNMjcxMDAxMTMxMzIyWjBnMQsw
|
||||
CQYDVQQGEwJVUzENMAsGA1UECAwEVXRhaDEOMAwGA1UEBwwFUHJvdm8xIzAhBgNV
|
||||
BAoMGkFDTUUgU2lnbmluZyBBdXRob3JpdHkgSW5jMRQwEgYDVQQDDAtleGFtcGxl
|
||||
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKlp3Kz278oyTDY4
|
||||
+1iNv1xMzWCJBWf7YxyYkXACVvZzUqgwxotNwAE/kE131HgpBJnu7HsDdS8nLy80
|
||||
mDkFCGmqEuVZMDaMGtcz3HJSklESjxYM1VNgifz2IlNmoaidNehFkCi1rz6cLMR0
|
||||
rEZpi98EbVZGl3n0vDD0eEZF5QQn/eDwclt/aKVBVLcQxo6MHhQdaK85a1uNyUU3
|
||||
HPUYiyPGVn9oX9PGcOpYFomJoBdg3sUVk32xKCUf15+uqVchdtyGFW/bp5KSBCnB
|
||||
QD0TzN+SgBHnVTzIfoi8WY56uuwnYauvT4fRh8SfkV91BzHBtWSbulNTfTUzgWhs
|
||||
VvCGw7ECAwEAAaNTMFEwHQYDVR0OBBYEFC4qTshaPoBie1ukhhM0KBi95NzJMB8G
|
||||
A1UdIwQYMBaAFC4qTshaPoBie1ukhhM0KBi95NzJMA8GA1UdEwEB/wQFMAMBAf8w
|
||||
DQYJKoZIhvcNAQELBQADggEBAB+fRdUHgqwpTWRfF+jQB4Af75HTfp6hgemUjapI
|
||||
eZn/OugS75/jfJt9npVsHl/aa63GL/W6kQShoMVOhrYqW52J1TSsLKZR2L7sv0ji
|
||||
KYfakv+aLkRKewPoVadsCL8GUmaCByE9mwlhmmZprkjDmA3hWsjEM5lyg7qleJ0k
|
||||
V32FVysdhLLnftt2SJB7lyoTujhkNAjJhLT/0Qr8t59v0sViPtL8532jSXqE1GK+
|
||||
zncsJDK7v2VEuurz1lPTRY6tPQOJ1Qt8vUzDH/ugcc5JPBEuHhjjrd5K65lxnGNw
|
||||
lnPHIS7FJm1OMkuatQXomNuuoWDPyM7fuVyGUUpmlkbpJsg=
|
||||
-----END CERTIFICATE-----
|
||||
28
node_modules/release-it/test/util/https-server/server/privkey.pem
generated
vendored
Normal file
28
node_modules/release-it/test/util/https-server/server/privkey.pem
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1+BYy7XcJoIAH
|
||||
qPcU7f74Dp/6N6f/hjLTADMIT1OO49W3Rrc6xbJjeLsiTD0Rj9Z/9CSI7Vh/AOLo
|
||||
W7OGMOZkJZNRw8L9lc14ZDB1FAcJITn8c9d0d0YltbxUOq509wEP6GSxYCqYZlvA
|
||||
jeyYADRW5PnP82n6MRMW9Ve5y50sijeaVk120je5zKvQTd0IR9rXf8K3M8CmnlEV
|
||||
uuf4+uXvg0gUsGvSSkBxPVGeUQHM/zRxj/I8WYCewb9Qs2TSJjgnFsfUCF6C3xs/
|
||||
T8p4vJ8PxeLs6GNcQhhtNH0q7WxaIV4cdrXkxPi6YS8ph1WErqNR2pdsCA5bUNTC
|
||||
T32vPjr/AgMBAAECggEAHeLCZpfYmpSpIljuR5o063GfdZ1pco6MT1ozh3Rb0VZ6
|
||||
9bBgDH+Gpk6gUWg7CWTZwkcLLw/oHme7XJUe/XWPiTggo2em4TYWumSeDsR8yVOT
|
||||
LfKqmp6yPyRDa4P9vgkJPB8bVoRoSoJZJF1K08YI0pKlsrEUITqpG3as8z9NL5C1
|
||||
65QdpCwiRjLnM6b7k93Vxa8xOnj51iDXV9uqGASCYmU+uoHG8HU8RzbVI4Xi/RCr
|
||||
PszmOi6AMtWPpzH2jDPiYnv96K50O/cOm/n3obuySdH0qeGR37sx94F82iCyiowo
|
||||
Ij+iU8VYK6gu27z811ehSlk3/4ey8ArlHvVew3TkqQKBgQD+K70baVqolcGfkDQY
|
||||
zgZ+4YlmncDrdhd0anHUEC15l7uPPHFBxrQlzRVTjhHCKn1sQTj/41isUWQAvcRR
|
||||
ZzjI+YaHQvvzJCSX6c113IWeemCcuTxenW6G/5BHJxOPddFnrcAAoeHhcNvni/CZ
|
||||
WrInTIcWn7eY5MMaxa4ELUoKRwKBgQC3R1R9be3BY36wPYcPkTZFxRAn2o7/Z2Ft
|
||||
MDbrddDZt12F7cuD8YnipI7TkJLQTXmg6P7u2XexcEyAUy2J+jdgfpM0N/HXu3Su
|
||||
YeYXHJfyGUK6bTlQT+trNuEyX5K7h2IbBstvH9A4PXEZPQ2I3aPZk3WsL630cMo6
|
||||
tbTy0o3tiQKBgQCv11qxSCXsVA7scTtZnc9ooGgKkkERpVV8uNefOsH7STn9UneY
|
||||
Zfvj2wpSEAvBJNw4tLbWcVa7gGOLD75uAteKUvb7RSBBilO2tY3raHEYvtlwE8bs
|
||||
PkZlJxGN6D7kFUKWU+JtjZFUAlxgyLPfpJt0DMG4qS6/nCROtUw6n4qFqQKBgB2S
|
||||
JrGuIORI91HcO4Rpe4Y6S2cCvnu65F9HnjTTZ4UZLr/DJEj/ma21u02rT+TH+03Z
|
||||
CfjjoYpBgjZaNUjD1Fd/VKTiOeUC28qfBQ7JkEKBjOCjatHocyVzT1ZfUT9skoml
|
||||
yQD+8wt/7lWSIjLo/9zFDAFiGAEOibJ7Sty62CdxAoGBAJLwVkDCT3yylDRqm2YC
|
||||
1uYh/xGLW4F/Jj7PjGNrSYClkbxPTpd4Nfa8EyZmUUya1rsfK1njXZKbwwpa2fbE
|
||||
+QVpXB18vaMW3hU7u63JDpLv2pCm91myPMQsF3xMc/S1vFAOWmXetYWax4/xiG2M
|
||||
SW4m9mBkzzF/2SpxQd9xy1Jx
|
||||
-----END PRIVATE KEY-----
|
||||
104
node_modules/release-it/test/util/index.js
generated
vendored
Normal file
104
node_modules/release-it/test/util/index.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import { mock } from 'node:test';
|
||||
import semver from 'semver';
|
||||
import { parseVersion } from '../../lib/util.js';
|
||||
import Config from '../../lib/config.js';
|
||||
import ShellStub from '../stub/shell.js';
|
||||
import Prompt from '../../lib/prompt.js';
|
||||
|
||||
const noop = Promise.resolve();
|
||||
|
||||
export class LogStub {
|
||||
constructor() {
|
||||
this.log = mock.fn();
|
||||
this.error = mock.fn();
|
||||
this.info = mock.fn();
|
||||
this.warn = mock.fn();
|
||||
this.verbose = mock.fn();
|
||||
this.exec = mock.fn();
|
||||
this.obtrusive = mock.fn();
|
||||
this.preview = mock.fn();
|
||||
}
|
||||
resetCalls() {
|
||||
this.log.mock.resetCalls();
|
||||
this.error.mock.resetCalls();
|
||||
this.info.mock.resetCalls();
|
||||
this.warn.mock.resetCalls();
|
||||
this.verbose.mock.resetCalls();
|
||||
this.exec.mock.resetCalls();
|
||||
this.obtrusive.mock.resetCalls();
|
||||
this.preview.mock.resetCalls();
|
||||
}
|
||||
}
|
||||
|
||||
export class SpinnerStub {
|
||||
show({ enabled = true, task }) {
|
||||
return enabled ? task() : noop;
|
||||
}
|
||||
}
|
||||
|
||||
export let factory = async (Definition, { namespace, options = {}, container = {} } = {}) => {
|
||||
options = Object.assign({}, { ci: true, verbose: false, 'dry-run': false, debug: false }, options);
|
||||
const ns = namespace || Definition.name.toLowerCase();
|
||||
container.config = container.config || new Config(Object.assign({ config: false }, options));
|
||||
container.log = new LogStub();
|
||||
await container.config.init();
|
||||
|
||||
container.spinner = new SpinnerStub();
|
||||
container.shell = container.shell || new ShellStub({ container });
|
||||
|
||||
container.prompt = container.prompt || new Prompt({ container });
|
||||
container.shell.cache = { set: () => {}, has: () => false };
|
||||
|
||||
return new Definition({
|
||||
namespace: ns,
|
||||
options,
|
||||
container
|
||||
});
|
||||
};
|
||||
|
||||
const getIncrement = plugin =>
|
||||
plugin.getIncrement(plugin.options) || plugin.getContext('increment') || plugin.config.getContext('increment');
|
||||
|
||||
const getVersion = async (plugin, options) => {
|
||||
const { latestVersion, increment } = options;
|
||||
return (
|
||||
(await plugin.getIncrementedVersionCI(options)) ||
|
||||
(await plugin.getIncrementedVersion(options)) ||
|
||||
(increment !== false ? semver.inc(latestVersion, increment || 'patch') : latestVersion)
|
||||
);
|
||||
};
|
||||
|
||||
export let runTasks = async plugin => {
|
||||
await plugin.init();
|
||||
|
||||
const name = (await plugin.getName()) || '__test__';
|
||||
const latestVersion = (await plugin.getLatestVersion()) || '1.0.0';
|
||||
const latestTag = plugin.config.getContext('latestTag');
|
||||
const changelog = (await plugin.getChangelog(latestVersion)) || null;
|
||||
const increment = getIncrement(plugin);
|
||||
|
||||
plugin.config.setContext({ name, latestVersion, latestTag, changelog });
|
||||
|
||||
const { preRelease } = plugin.config.options;
|
||||
const isPreRelease = Boolean(preRelease);
|
||||
const preReleaseId = typeof preRelease === 'string' ? preRelease : null;
|
||||
const version = await getVersion(plugin, { latestVersion, increment, isPreRelease, preReleaseId });
|
||||
|
||||
plugin.config.setContext(parseVersion(version));
|
||||
|
||||
await plugin.beforeBump();
|
||||
await plugin.bump(version);
|
||||
|
||||
const tagName = plugin.config.getContext('tagName') || version;
|
||||
plugin.config.setContext({ tagName });
|
||||
|
||||
await plugin.beforeRelease();
|
||||
await plugin.release();
|
||||
await plugin.afterRelease();
|
||||
|
||||
return {
|
||||
name,
|
||||
latestVersion,
|
||||
version
|
||||
};
|
||||
};
|
||||
11
node_modules/release-it/test/util/mock.js
generated
vendored
Normal file
11
node_modules/release-it/test/util/mock.js
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { MockServer, FetchMocker } from 'mentoss';
|
||||
|
||||
export const mockFetch = baseUrls => {
|
||||
const servers = [baseUrls].flat().map(url => new MockServer(url));
|
||||
|
||||
const mocker = new FetchMocker({
|
||||
servers
|
||||
});
|
||||
|
||||
return [mocker, ...servers];
|
||||
};
|
||||
18
node_modules/release-it/test/util/sh.js
generated
vendored
Normal file
18
node_modules/release-it/test/util/sh.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const getCommandAndArgs = input => {
|
||||
if (typeof input !== 'string' || !input.trim()) {
|
||||
throw new Error('Invalid input: expected a non-empty string.');
|
||||
}
|
||||
|
||||
const [command, ...args] = input.trim().split(/\s+/);
|
||||
|
||||
return [command, args];
|
||||
};
|
||||
|
||||
const exec = (command, opts = { stdio: 'inherit' }) => {
|
||||
const [cmd, args] = getCommandAndArgs(command);
|
||||
return spawnSync(cmd, args, opts);
|
||||
};
|
||||
|
||||
export default { getCommandAndArgs, exec };
|
||||
145
node_modules/release-it/test/utils.js
generated
vendored
Normal file
145
node_modules/release-it/test/utils.js
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
import { EOL } from 'node:os';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { stripVTControlCharacters } from 'node:util';
|
||||
import mockStdIo from 'mock-stdio';
|
||||
import { format, truncateLines, parseGitUrl, parseVersion, get } from '../lib/util.js';
|
||||
|
||||
test('format', () => {
|
||||
assert.equal(format('release v${version}', { version: '1.0.0' }), 'release v1.0.0');
|
||||
assert.equal(format('release v${version} (${name})', { version: '1.0.0', name: 'foo' }), 'release v1.0.0 (foo)');
|
||||
assert.equal(format('release v${version} (${name})', { version: '1.0.0', name: 'foo' }), 'release v1.0.0 (foo)');
|
||||
});
|
||||
|
||||
test('format (throw)', () => {
|
||||
mockStdIo.start();
|
||||
assert.throws(() => format('release v${foo}', { version: '1.0.0' }), /foo is not defined/);
|
||||
const { stdout, stderr } = mockStdIo.end();
|
||||
assert.equal(stdout, '');
|
||||
assert.match(
|
||||
stripVTControlCharacters(stderr),
|
||||
/ERROR Unable to render template with context:\s+release v\${foo}\s+{"version":"1\.0\.0"}\s+ERROR ReferenceError: foo is not defined/
|
||||
);
|
||||
});
|
||||
|
||||
test('truncateLines', () => {
|
||||
const input = `1${EOL}2${EOL}3${EOL}4${EOL}5${EOL}6`;
|
||||
assert.equal(truncateLines(input), input);
|
||||
assert.equal(truncateLines(input, 3), `1${EOL}2${EOL}3${EOL}...and 3 more`);
|
||||
assert.equal(truncateLines(input, 1, '...'), `1...`);
|
||||
});
|
||||
|
||||
test('parseGitUrl', () => {
|
||||
assert.deepEqual(parseGitUrl('https://github.com/webpro/release-it.git'), {
|
||||
host: 'github.com',
|
||||
owner: 'webpro',
|
||||
project: 'release-it',
|
||||
protocol: 'https',
|
||||
remote: 'https://github.com/webpro/release-it.git',
|
||||
repository: 'webpro/release-it'
|
||||
});
|
||||
|
||||
assert.deepEqual(parseGitUrl('git@gitlab.com:org/sub-group/repo-in-sub-group.git'), {
|
||||
host: 'gitlab.com',
|
||||
owner: 'org/sub-group',
|
||||
project: 'repo-in-sub-group',
|
||||
protocol: 'ssh',
|
||||
remote: 'git@gitlab.com:org/sub-group/repo-in-sub-group.git',
|
||||
repository: 'org/sub-group/repo-in-sub-group'
|
||||
});
|
||||
|
||||
assert.deepEqual(parseGitUrl('git@github.com:org/example.com.git'), {
|
||||
host: 'github.com',
|
||||
owner: 'org',
|
||||
project: 'example.com',
|
||||
protocol: 'ssh',
|
||||
remote: 'git@github.com:org/example.com.git',
|
||||
repository: 'org/example.com'
|
||||
});
|
||||
|
||||
assert.deepEqual(parseGitUrl('file://Users/john/doe/owner/project'), {
|
||||
host: 'users',
|
||||
owner: 'owner',
|
||||
project: 'project',
|
||||
protocol: 'file',
|
||||
remote: 'file://users/john/doe/owner/project',
|
||||
repository: 'owner/project'
|
||||
});
|
||||
|
||||
assert.deepEqual(parseGitUrl('/Users/john/doe/owner/project'), {
|
||||
host: 'users',
|
||||
owner: 'owner',
|
||||
project: 'project',
|
||||
protocol: 'file',
|
||||
remote: 'file://users/john/doe/owner/project',
|
||||
repository: 'owner/project'
|
||||
});
|
||||
|
||||
assert.deepEqual(parseGitUrl('C:\\\\Users\\john\\doe\\owner\\project'), {
|
||||
host: 'users',
|
||||
owner: 'owner',
|
||||
project: 'project',
|
||||
protocol: 'file',
|
||||
remote: 'file://users/john/doe/owner/project',
|
||||
repository: 'owner/project'
|
||||
});
|
||||
});
|
||||
|
||||
test('parseVersion', () => {
|
||||
assert.deepEqual(parseVersion(), { version: undefined, isPreRelease: false, preReleaseId: null });
|
||||
assert.deepEqual(parseVersion(0), { version: '0.0.0', isPreRelease: false, preReleaseId: null });
|
||||
assert.deepEqual(parseVersion(1), { version: '1.0.0', isPreRelease: false, preReleaseId: null });
|
||||
assert.deepEqual(parseVersion('1'), { version: '1.0.0', isPreRelease: false, preReleaseId: null });
|
||||
assert.deepEqual(parseVersion('1.0'), { version: '1.0.0', isPreRelease: false, preReleaseId: null });
|
||||
assert.deepEqual(parseVersion('1.0.0'), { version: '1.0.0', isPreRelease: false, preReleaseId: null });
|
||||
assert.deepEqual(parseVersion('1.0.0-0'), { version: '1.0.0-0', isPreRelease: true, preReleaseId: null });
|
||||
assert.deepEqual(parseVersion('1.0.0-next.1'), { version: '1.0.0-next.1', isPreRelease: true, preReleaseId: 'next' });
|
||||
assert.deepEqual(parseVersion('21.04.1'), { version: '21.04.1', isPreRelease: false, preReleaseId: null });
|
||||
});
|
||||
|
||||
const sample = {
|
||||
root: {
|
||||
level1: {
|
||||
level2: {
|
||||
value: 'nested'
|
||||
},
|
||||
array: [
|
||||
{ id: 1, data: 'first' },
|
||||
{ id: 2, data: 'second' }
|
||||
],
|
||||
'key.with.dot': {
|
||||
special: true
|
||||
}
|
||||
},
|
||||
mixed: [{ deep: { value: 100 } }, { deep: { value: 200 } }]
|
||||
}
|
||||
};
|
||||
|
||||
test('get: accesses a simple nested property', () => {
|
||||
assert.equal(get(sample, 'root.level1.level2.value'), 'nested');
|
||||
});
|
||||
|
||||
test('get: accesses array elements by index', () => {
|
||||
assert.equal(get(sample, 'root.level1.array[0].data'), 'first');
|
||||
assert.equal(get(sample, 'root.level1.array[1].id'), 2);
|
||||
});
|
||||
|
||||
test('get: accesses keys with dots using bracket notation', () => {
|
||||
assert.equal(get(sample, 'root.level1["key.with.dot"].special'), true);
|
||||
});
|
||||
|
||||
test('get: navigates mixed objects and arrays', () => {
|
||||
assert.equal(get(sample, 'root.mixed[0].deep.value'), 100);
|
||||
assert.equal(get(sample, 'root.mixed[1].deep.value'), 200);
|
||||
});
|
||||
|
||||
test('get: returns default value for non-existent properties', () => {
|
||||
assert.equal(get(sample, 'root.level1.unknown', 'default'), 'default');
|
||||
assert.equal(get(sample, 'root.level1.array[10].id', null), null);
|
||||
});
|
||||
|
||||
test('get: handles empty path and null/undefined objects', () => {
|
||||
assert.equal(get(sample, '', 'default'), 'default');
|
||||
assert.equal(get(null, 'any.path', 'default'), 'default');
|
||||
assert.equal(get(undefined, 'any.path', 'default'), 'default');
|
||||
});
|
||||
217
node_modules/release-it/test/version.js
generated
vendored
Normal file
217
node_modules/release-it/test/version.js
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
import test, { describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import Version from '../lib/plugin/version/Version.js';
|
||||
import { factory, runTasks } from './util/index.js';
|
||||
|
||||
describe('version', () => {
|
||||
test('isValidVersion', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(v.isValid('1.0.0'), true);
|
||||
assert.equal(v.isValid(1.0), false);
|
||||
});
|
||||
|
||||
test('isPreRelease', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(v.isPreRelease('1.0.0-beta.0'), true);
|
||||
assert.equal(v.isPreRelease('1.0.0'), false);
|
||||
});
|
||||
|
||||
test('should return the same version in both interactive and ci mode', async () => {
|
||||
const v = await factory(Version);
|
||||
const options = { latestVersion: '2.0.0-beta.1', increment: null, preReleaseId: 'rc', isPreRelease: true };
|
||||
const resultInteractiveMode = await v.getIncrementedVersion(options);
|
||||
assert.equal(resultInteractiveMode, '2.0.0-rc.0');
|
||||
const resultCiMode = v.getIncrementedVersionCI(options);
|
||||
assert.equal(resultInteractiveMode, resultCiMode);
|
||||
});
|
||||
|
||||
test('should increment latest version', async () => {
|
||||
const v = await factory(Version);
|
||||
const latestVersion = '1.0.0';
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: false }), '1.0.0');
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: 'foo' }), undefined);
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: 'patsj' }), undefined);
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: 'a.b.c' }), undefined);
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: '0.9.0' }), undefined);
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: '1.1.0' }), '1.1.0');
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: 'major' }), '2.0.0');
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: '2.0.0-beta.1' }), '2.0.0-beta.1');
|
||||
});
|
||||
|
||||
test('should not increment latest version in interactive mode', async () => {
|
||||
const v = await factory(Version, { options: { ci: false } });
|
||||
const latestVersion = '1.0.0';
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: null }), undefined);
|
||||
assert.equal(v.incrementVersion({ latestVersion, increment: false }), '1.0.0');
|
||||
});
|
||||
|
||||
test('should always set increment version in CI mode', async () => {
|
||||
const v = await factory(Version, { options: { ci: true } });
|
||||
const latestVersion = '1.0.0';
|
||||
assert.equal(v.getIncrementedVersionCI({ latestVersion, increment: false }), '1.0.0');
|
||||
assert.equal(v.getIncrementedVersionCI({ latestVersion, increment: null }), '1.0.1');
|
||||
assert.equal(v.getIncrementedVersionCI({ latestVersion, increment: '1.1.0' }), '1.1.0');
|
||||
assert.equal(v.getIncrementedVersionCI({ latestVersion, increment: 'major' }), '2.0.0');
|
||||
});
|
||||
|
||||
test('should increment latest version (coerce)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(v.incrementVersion({ increment: '1.2' }), '1.2.0');
|
||||
assert.equal(v.incrementVersion({ increment: '1' }), '1.0.0');
|
||||
assert.equal(v.incrementVersion({ increment: 'v1.2.0.0' }), '1.2.0');
|
||||
});
|
||||
|
||||
test('should increment version (pre-release continuation)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(v.incrementVersion({ latestVersion: '1.2.3-alpha.0', increment: 'prepatch' }), '1.2.4-0');
|
||||
});
|
||||
|
||||
test('should increment version (prepatch)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({ latestVersion: '1.2.3', increment: 'prepatch', preReleaseId: 'alpha' }),
|
||||
'1.2.4-alpha.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment version (normalized)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({ latestVersion: '1.2.3', increment: 'patch', preReleaseId: 'alpha', isPreRelease: true }),
|
||||
'1.2.4-alpha.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment version (prepatch on prerelease version)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({ latestVersion: '1.2.3-alpha.5', increment: 'prepatch', preReleaseId: 'next' }),
|
||||
'1.2.4-next.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment version (normalized on prerelease version)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({
|
||||
latestVersion: '1.2.3-alpha.5',
|
||||
increment: 'patch',
|
||||
preReleaseId: 'next',
|
||||
isPreRelease: true
|
||||
}),
|
||||
'1.2.4-next.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment version (prerelease)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({ latestVersion: '1.2.3', increment: 'prerelease', preReleaseId: 'alpha' }),
|
||||
'1.2.4-alpha.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment version (prerelease cont.)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(v.incrementVersion({ latestVersion: '1.2.3-alpha.0', increment: 'prerelease' }), '1.2.3-alpha.1');
|
||||
});
|
||||
|
||||
test('should increment version (preReleaseId continuation)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({ latestVersion: '1.2.3-alpha.0', increment: 'prerelease', preReleaseId: 'alpha' }),
|
||||
'1.2.3-alpha.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment version (prepatch/preReleaseId continuation)', async () => {
|
||||
const v = await factory(Version);
|
||||
const options = {
|
||||
latestVersion: '1.2.3-beta.0',
|
||||
increment: 'prerelease',
|
||||
preReleaseId: 'beta',
|
||||
isPreRelease: true
|
||||
};
|
||||
assert.equal(v.incrementVersion(options), '1.2.3-beta.1');
|
||||
});
|
||||
|
||||
test('should increment version (preReleaseId w/o preRelease)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({ latestVersion: '1.2.3-alpha.0', increment: 'patch', preReleaseId: 'alpha' }),
|
||||
'1.2.3'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment version (non-numeric prepatch continuation)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(v.incrementVersion({ latestVersion: '1.2.3-alpha', increment: 'prerelease' }), '1.2.3-alpha.0');
|
||||
});
|
||||
|
||||
test('should increment version (patch release after pre-release)', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(v.incrementVersion({ latestVersion: '1.2.3-alpha.1', increment: 'patch' }), '1.2.3');
|
||||
});
|
||||
|
||||
test('should increment version and start at base 1', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({
|
||||
latestVersion: '1.3.0',
|
||||
increment: 'major',
|
||||
isPreRelease: true,
|
||||
preReleaseId: 'beta',
|
||||
preReleaseBase: '1'
|
||||
}),
|
||||
'2.0.0-beta.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('should increment prerelease version and ignore prelease base 1', async () => {
|
||||
const v = await factory(Version);
|
||||
assert.equal(
|
||||
v.incrementVersion({
|
||||
latestVersion: '1.2.3-alpha.5',
|
||||
increment: 'prerelease',
|
||||
preReleaseId: 'alpha',
|
||||
isPreRelease: true,
|
||||
preReleaseBase: '1'
|
||||
}),
|
||||
'1.2.3-alpha.6'
|
||||
);
|
||||
});
|
||||
|
||||
test('should run tasks without errors', async t => {
|
||||
const options = { version: { increment: 'minor' } };
|
||||
const v = await factory(Version, { options });
|
||||
const getIncrement = t.mock.method(v, 'getIncrement');
|
||||
const getIncrementedVersionCI = t.mock.method(v, 'getIncrementedVersionCI');
|
||||
const incrementVersion = t.mock.method(v, 'incrementVersion');
|
||||
|
||||
await runTasks(v);
|
||||
|
||||
assert.equal(getIncrement.mock.callCount(), 1);
|
||||
assert.deepEqual(getIncrement.mock.calls[0].arguments[0], { increment: 'minor' });
|
||||
assert.equal(getIncrementedVersionCI.mock.callCount(), 1);
|
||||
assert.deepEqual(getIncrementedVersionCI.mock.calls[0].arguments[0], {
|
||||
latestVersion: '1.0.0',
|
||||
increment: 'minor',
|
||||
isPreRelease: false,
|
||||
preReleaseId: null
|
||||
});
|
||||
assert.equal(await incrementVersion.mock.calls[0].result, '1.1.0');
|
||||
assert.equal(incrementVersion.mock.callCount(), 1);
|
||||
assert.deepEqual(incrementVersion.mock.calls[0].arguments[0], {
|
||||
latestVersion: '1.0.0',
|
||||
increment: 'minor',
|
||||
isPreRelease: false,
|
||||
preReleaseId: null
|
||||
});
|
||||
assert.equal(incrementVersion.mock.calls[0].result, '1.1.0');
|
||||
const { latestVersion, version, isPreRelease, preReleaseId } = v.config.getContext();
|
||||
assert.equal(latestVersion, '1.0.0');
|
||||
assert.equal(version, '1.1.0');
|
||||
assert.equal(isPreRelease, false);
|
||||
assert.equal(preReleaseId, null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user