first commit
This commit is contained in:
126
node_modules/release-it/lib/plugin/GitBase.js
generated
vendored
Normal file
126
node_modules/release-it/lib/plugin/GitBase.js
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
import { EOL } from 'node:os';
|
||||
import { format, parseGitUrl } from '../util.js';
|
||||
import Plugin from './Plugin.js';
|
||||
|
||||
const options = { write: false };
|
||||
const changelogFallback = 'git log --pretty=format:"* %s (%h)"';
|
||||
|
||||
class GitBase extends Plugin {
|
||||
async init() {
|
||||
const remoteUrl = await this.getRemoteUrl();
|
||||
await this.fetch(remoteUrl);
|
||||
|
||||
const branchName = await this.getBranchName();
|
||||
const repo = parseGitUrl(remoteUrl);
|
||||
this.setContext({ remoteUrl, branchName, repo });
|
||||
this.config.setContext({ remoteUrl, branchName, repo });
|
||||
|
||||
const latestTag = await this.getLatestTagName();
|
||||
const secondLatestTag = !this.config.isIncrement ? await this.getSecondLatestTagName(latestTag) : null;
|
||||
const tagTemplate = this.options.tagName || ((latestTag || '').match(/^v/) ? 'v${version}' : '${version}');
|
||||
this.config.setContext({ latestTag, secondLatestTag, tagTemplate });
|
||||
}
|
||||
|
||||
getName() {
|
||||
const repo = this.getContext('repo');
|
||||
return repo.project;
|
||||
}
|
||||
|
||||
getLatestVersion() {
|
||||
const { tagTemplate, latestTag } = this.config.getContext();
|
||||
const prefix = format(tagTemplate.replace(/\$\{version\}/, ''), this.config.getContext());
|
||||
return latestTag ? latestTag.replace(prefix, '').replace(/^v/, '') : null;
|
||||
}
|
||||
|
||||
async getChangelog() {
|
||||
const { snapshot } = this.config.getContext();
|
||||
const { latestTag, secondLatestTag } = this.config.getContext();
|
||||
const context = { latestTag, from: latestTag, to: 'HEAD' };
|
||||
const { changelog } = this.options;
|
||||
if (!changelog) return null;
|
||||
|
||||
if (latestTag && !this.config.isIncrement) {
|
||||
context.from = secondLatestTag;
|
||||
context.to = `${latestTag}^1`;
|
||||
}
|
||||
|
||||
// For now, snapshots do not get a changelog, as it often goes haywire (easy to add to release manually)
|
||||
if (snapshot) return '';
|
||||
|
||||
if (!context.from && changelog.includes('${from}')) {
|
||||
return this.exec(changelogFallback);
|
||||
}
|
||||
|
||||
return this.exec(changelog, { context, options });
|
||||
}
|
||||
|
||||
bump(version) {
|
||||
const { tagTemplate } = this.config.getContext();
|
||||
const context = Object.assign(this.config.getContext(), { version });
|
||||
const tagName = format(tagTemplate, context) || version;
|
||||
this.setContext({ version });
|
||||
this.config.setContext({ tagName });
|
||||
}
|
||||
|
||||
isRemoteName(remoteUrlOrName) {
|
||||
return remoteUrlOrName && !remoteUrlOrName.includes('/');
|
||||
}
|
||||
|
||||
async getRemoteUrl() {
|
||||
const remoteNameOrUrl = this.options.pushRepo || (await this.getRemote()) || 'origin';
|
||||
return this.isRemoteName(remoteNameOrUrl)
|
||||
? this.exec(`git remote get-url ${remoteNameOrUrl}`, { options }).catch(() =>
|
||||
this.exec(`git config --get remote.${remoteNameOrUrl}.url`, { options }).catch(() => null)
|
||||
)
|
||||
: remoteNameOrUrl;
|
||||
}
|
||||
|
||||
async getRemote() {
|
||||
const branchName = await this.getBranchName();
|
||||
return branchName ? await this.getRemoteForBranch(branchName) : null;
|
||||
}
|
||||
|
||||
getBranchName() {
|
||||
return this.exec('git rev-parse --abbrev-ref HEAD', { options }).catch(() => null);
|
||||
}
|
||||
|
||||
getRemoteForBranch(branch) {
|
||||
return this.exec(`git config --get branch.${branch}.remote`, { options }).catch(() => null);
|
||||
}
|
||||
|
||||
fetch(remoteUrl) {
|
||||
return this.exec('git fetch').catch(err => {
|
||||
this.debug(err);
|
||||
throw new Error(`Unable to fetch from ${remoteUrl}${EOL}${err.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
getLatestTagName() {
|
||||
const context = Object.assign({}, this.config.getContext(), { version: '*' });
|
||||
const match = format(this.options.tagMatch || this.options.tagName || '${version}', context);
|
||||
const exclude = this.options.tagExclude ? ` --exclude=${format(this.options.tagExclude, context)}` : '';
|
||||
if (this.options.getLatestTagFromAllRefs) {
|
||||
return this.exec(
|
||||
`git -c "versionsort.suffix=-" for-each-ref --count=1 --sort=-v:refname --format="%(refname:short)" refs/tags/${match}`,
|
||||
{ options }
|
||||
).then(
|
||||
stdout => stdout || null,
|
||||
() => null
|
||||
);
|
||||
} else {
|
||||
return this.exec(`git describe --tags --match=${match} --abbrev=0${exclude}`, { options }).then(
|
||||
stdout => stdout || null,
|
||||
() => null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getSecondLatestTagName(latestTag) {
|
||||
const sha = await this.exec(`git rev-list ${latestTag || '--skip=1'} --tags --max-count=1`, {
|
||||
options
|
||||
});
|
||||
return this.exec(`git describe --tags --abbrev=0 "${sha}^"`, { options }).catch(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
export default GitBase;
|
||||
65
node_modules/release-it/lib/plugin/GitRelease.js
generated
vendored
Normal file
65
node_modules/release-it/lib/plugin/GitRelease.js
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { pick, readJSON } from '../util.js';
|
||||
import GitBase from './GitBase.js';
|
||||
|
||||
const defaultConfig = readJSON(new URL('../../config/release-it.json', import.meta.url));
|
||||
|
||||
class GitRelease extends GitBase {
|
||||
static isEnabled(options) {
|
||||
return options.release;
|
||||
}
|
||||
|
||||
getInitialOptions(options) {
|
||||
const baseOptions = super.getInitialOptions(...arguments);
|
||||
const git = options.git || defaultConfig.git;
|
||||
const gitOptions = pick(git, [
|
||||
'tagExclude',
|
||||
'tagName',
|
||||
'tagMatch',
|
||||
'getLatestTagFromAllRefs',
|
||||
'pushRepo',
|
||||
'changelog',
|
||||
'commit'
|
||||
]);
|
||||
|
||||
return Object.assign({}, gitOptions, baseOptions);
|
||||
}
|
||||
|
||||
get token() {
|
||||
const { tokenRef } = this.options;
|
||||
return process.env[tokenRef] || null;
|
||||
}
|
||||
|
||||
async beforeRelease() {
|
||||
const { releaseNotes: script } = this.options;
|
||||
const { changelog } = this.config.getContext();
|
||||
const releaseNotes =
|
||||
typeof script === 'function' || typeof script === 'string' ? await this.processReleaseNotes(script) : changelog;
|
||||
this.setContext({ releaseNotes });
|
||||
if (releaseNotes !== changelog) {
|
||||
this.log.preview({ title: 'release notes', text: releaseNotes });
|
||||
}
|
||||
}
|
||||
|
||||
async processReleaseNotes(script) {
|
||||
if (typeof script === 'function') {
|
||||
const ctx = Object.assign({}, this.config.getContext(), { [this.namespace]: this.getContext() });
|
||||
return script(ctx);
|
||||
}
|
||||
|
||||
if (typeof script === 'string') {
|
||||
return this.exec(script);
|
||||
}
|
||||
}
|
||||
|
||||
afterRelease() {
|
||||
const { isReleased, releaseUrl, discussionUrl } = this.getContext();
|
||||
if (isReleased) {
|
||||
this.log.log(`🔗 ${releaseUrl}`);
|
||||
}
|
||||
if (discussionUrl) {
|
||||
this.log.log(`🔗 ${discussionUrl}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GitRelease;
|
||||
75
node_modules/release-it/lib/plugin/Plugin.js
generated
vendored
Normal file
75
node_modules/release-it/lib/plugin/Plugin.js
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import { debug } from 'node:util';
|
||||
import merge from 'lodash.merge';
|
||||
import { get } from '../util.js';
|
||||
|
||||
class Plugin {
|
||||
static isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static disablePlugin() {
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor({ namespace, options = {}, container = {} } = {}) {
|
||||
this.namespace = namespace;
|
||||
this.options = Object.freeze(this.getInitialOptions(options, namespace));
|
||||
this.context = {};
|
||||
this.config = container.config;
|
||||
this.log = container.log;
|
||||
this.shell = container.shell;
|
||||
this.spinner = container.spinner;
|
||||
this.prompt = container.prompt;
|
||||
this.debug = debug(`release-it:${namespace}`);
|
||||
}
|
||||
|
||||
getInitialOptions(options, namespace) {
|
||||
return options[namespace] || {};
|
||||
}
|
||||
|
||||
init() {}
|
||||
getName() {}
|
||||
getLatestVersion() {}
|
||||
getChangelog() {}
|
||||
getIncrement() {}
|
||||
getIncrementedVersionCI() {}
|
||||
getIncrementedVersion() {}
|
||||
beforeBump() {}
|
||||
bump() {}
|
||||
beforeRelease() {}
|
||||
release() {}
|
||||
afterRelease() {}
|
||||
|
||||
getContext(path) {
|
||||
const context = merge({}, this.options, this.context);
|
||||
|
||||
return path ? get(context, path) : context;
|
||||
}
|
||||
|
||||
setContext(context) {
|
||||
merge(this.context, context);
|
||||
}
|
||||
|
||||
exec(command, { options, context = {} } = {}) {
|
||||
const ctx = Object.assign(context, this.config.getContext(), { [this.namespace]: this.getContext() });
|
||||
return this.shell.exec(command, options, ctx);
|
||||
}
|
||||
|
||||
registerPrompts(prompts) {
|
||||
this.prompt.register(prompts, this.namespace);
|
||||
}
|
||||
|
||||
async showPrompt(options) {
|
||||
options.namespace = this.namespace;
|
||||
return this.prompt.show(options);
|
||||
}
|
||||
|
||||
step(options) {
|
||||
const context = Object.assign({}, this.config.getContext(), { [this.namespace]: this.getContext() });
|
||||
const opts = Object.assign({}, options, { context });
|
||||
const isException = this.config.isPromptOnlyVersion && ['incrementList', 'publish', 'otp'].includes(opts.prompt);
|
||||
return this.config.isCI && !isException ? this.spinner.show(opts) : this.showPrompt(opts);
|
||||
}
|
||||
}
|
||||
|
||||
export default Plugin;
|
||||
89
node_modules/release-it/lib/plugin/factory.js
generated
vendored
Normal file
89
node_modules/release-it/lib/plugin/factory.js
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import url from 'node:url';
|
||||
import path from 'node:path';
|
||||
import util from 'node:util';
|
||||
import { createRequire } from 'node:module';
|
||||
import { castArray } from '../util.js';
|
||||
import Version from './version/Version.js';
|
||||
import Git from './git/Git.js';
|
||||
import GitLab from './gitlab/GitLab.js';
|
||||
import GitHub from './github/GitHub.js';
|
||||
import npm from './npm/npm.js';
|
||||
|
||||
const debug = util.debug('release-it:plugins');
|
||||
|
||||
const pluginNames = ['npm', 'git', 'github', 'gitlab', 'version'];
|
||||
|
||||
const plugins = {
|
||||
version: Version,
|
||||
git: Git,
|
||||
gitlab: GitLab,
|
||||
github: GitHub,
|
||||
npm: npm
|
||||
};
|
||||
|
||||
const load = async pluginName => {
|
||||
let plugin = null;
|
||||
try {
|
||||
const module = await import(pluginName);
|
||||
plugin = module.default;
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
try {
|
||||
const module = await import(path.join(process.cwd(), pluginName));
|
||||
plugin = module.default;
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
// In some cases or tests we might need to support legacy `require.resolve`
|
||||
const require = createRequire(process.cwd());
|
||||
const module = await import(url.pathToFileURL(require.resolve(pluginName, { paths: [process.cwd()] })));
|
||||
plugin = module.default;
|
||||
}
|
||||
}
|
||||
return [getPluginName(pluginName), plugin];
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export const getPluginName = pluginName => {
|
||||
if (pluginName.startsWith('.')) {
|
||||
return path.parse(pluginName).name;
|
||||
}
|
||||
|
||||
return pluginName;
|
||||
};
|
||||
|
||||
export let getPlugins = async (config, container) => {
|
||||
const context = config.getContext();
|
||||
const disabledPlugins = [];
|
||||
const enabledExternalPlugins = [];
|
||||
if (context.plugins) {
|
||||
for (const [pluginName, pluginConfig] of Object.entries(context.plugins)) {
|
||||
const [name, Plugin] = await load(pluginName);
|
||||
const [namespace, options] = pluginConfig.length === 2 ? pluginConfig : [name, pluginConfig];
|
||||
|
||||
config.setContext({ [namespace]: options });
|
||||
|
||||
if (await Plugin.isEnabled(options)) {
|
||||
const instance = new Plugin({ namespace, options: config.getContext(), container });
|
||||
debug({ namespace, options: instance.options });
|
||||
enabledExternalPlugins.push(instance);
|
||||
|
||||
disabledPlugins.push(...pluginNames.filter(p => castArray(Plugin.disablePlugin(options)).includes(p)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const enabledPlugins = await pluginNames.reduce(async (result, plugin) => {
|
||||
if (plugin in plugins && !disabledPlugins.includes(plugin)) {
|
||||
const Plugin = plugins[plugin];
|
||||
const pluginOptions = context[plugin];
|
||||
if (await Plugin.isEnabled(pluginOptions)) {
|
||||
const instance = new Plugin({ namespace: plugin, options: context, container });
|
||||
debug({ namespace: plugin, options: instance.options });
|
||||
(await result).push(instance);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
return [enabledPlugins, enabledExternalPlugins];
|
||||
};
|
||||
242
node_modules/release-it/lib/plugin/git/Git.js
generated
vendored
Normal file
242
node_modules/release-it/lib/plugin/git/Git.js
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
import { EOL } from 'node:os';
|
||||
import { spawn } from 'node:child_process';
|
||||
import matcher from 'wildcard-match';
|
||||
import { format, e, fixArgs, once, castArray } from '../../util.js';
|
||||
import GitBase from '../GitBase.js';
|
||||
import prompts from './prompts.js';
|
||||
|
||||
const noop = Promise.resolve();
|
||||
const invalidPushRepoRe = /^\S+@/;
|
||||
const options = { write: false };
|
||||
|
||||
const docs = 'https://git.io/release-it-git';
|
||||
|
||||
async function isGitRepo() {
|
||||
return await new Promise(resolve => {
|
||||
const process = spawn('git', ['rev-parse', '--git-dir']);
|
||||
process.on('close', code => resolve(code === 0));
|
||||
process.on('error', () => resolve(false));
|
||||
});
|
||||
}
|
||||
|
||||
class Git extends GitBase {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.registerPrompts(prompts);
|
||||
}
|
||||
|
||||
static async isEnabled(options) {
|
||||
return options !== false && (await isGitRepo());
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.options.requireBranch && !(await this.isRequiredBranch(this.options.requireBranch))) {
|
||||
throw e(`Must be on branch ${this.options.requireBranch}`, docs);
|
||||
}
|
||||
if (this.options.requireCleanWorkingDir && !(await this.isWorkingDirClean())) {
|
||||
throw e(`Working dir must be clean.${EOL}Please stage and commit your changes.`, docs);
|
||||
}
|
||||
|
||||
await super.init();
|
||||
|
||||
const remoteUrl = this.getContext('remoteUrl');
|
||||
if (this.options.push && !remoteUrl) {
|
||||
throw e(`Could not get remote Git url.${EOL}Please add a remote repository.`, docs);
|
||||
}
|
||||
if (this.options.requireUpstream && !(await this.hasUpstreamBranch())) {
|
||||
throw e(`No upstream configured for current branch.${EOL}Please set an upstream branch.`, docs);
|
||||
}
|
||||
if (this.options.requireCommits && (await this.getCommitsSinceLatestTag(this.options.commitsPath)) === 0) {
|
||||
throw e(`There are no commits since the latest tag.`, docs, this.options.requireCommitsFail);
|
||||
}
|
||||
}
|
||||
|
||||
rollback() {
|
||||
this.log.info('Rolling back changes...');
|
||||
const { tagName } = this.config.getContext();
|
||||
const { isCommitted, isTagged } = this.getContext();
|
||||
if (isTagged) {
|
||||
this.log.info(`Deleting local tag ${tagName}`);
|
||||
this.exec(`git tag --delete ${tagName}`);
|
||||
}
|
||||
|
||||
this.log.info(`Resetting local changes made`);
|
||||
this.exec(`git reset --hard HEAD${isCommitted ? '~1' : ''}`);
|
||||
}
|
||||
|
||||
enableRollback() {
|
||||
this.rollbackOnce = once(this.rollback.bind(this));
|
||||
process.on('SIGINT', this.rollbackOnce);
|
||||
process.on('exit', this.rollbackOnce);
|
||||
}
|
||||
|
||||
disableRollback() {
|
||||
if (this.rollbackOnce) {
|
||||
process.removeListener('SIGINT', this.rollbackOnce);
|
||||
process.removeListener('exit', this.rollbackOnce);
|
||||
}
|
||||
}
|
||||
|
||||
async beforeRelease() {
|
||||
if (this.options.commit) {
|
||||
if (this.options.requireCleanWorkingDir) {
|
||||
this.enableRollback();
|
||||
}
|
||||
const changeSet = await this.status();
|
||||
this.log.preview({ title: 'changeset', text: changeSet });
|
||||
await this.stageDir();
|
||||
}
|
||||
}
|
||||
|
||||
async release() {
|
||||
const { commit, tag, push } = this.options;
|
||||
await this.step({ enabled: commit, task: () => this.commit(), label: 'Git commit', prompt: 'commit' });
|
||||
await this.step({ enabled: tag, task: () => this.tag(), label: 'Git tag', prompt: 'tag' });
|
||||
return !!(await this.step({ enabled: push, task: () => this.push(), label: 'Git push', prompt: 'push' }));
|
||||
}
|
||||
|
||||
async isRequiredBranch() {
|
||||
const branch = await this.getBranchName();
|
||||
const requiredBranches = castArray(this.options.requireBranch);
|
||||
const [branches, negated] = requiredBranches.reduce(
|
||||
([p, n], b) => (b.startsWith('!') ? [p, [...n, b.slice(1)]] : [[...p, b], n]),
|
||||
[[], []]
|
||||
);
|
||||
return (
|
||||
(branches.length > 0 ? matcher(branches)(branch) : true) &&
|
||||
(negated.length > 0 ? !matcher(negated)(branch) : true)
|
||||
);
|
||||
}
|
||||
|
||||
async hasUpstreamBranch() {
|
||||
const ref = await this.exec('git symbolic-ref HEAD', { options });
|
||||
const branch = await this.exec(`git for-each-ref --format="%(upstream:short)" ${ref}`, { options }).catch(
|
||||
() => null
|
||||
);
|
||||
return Boolean(branch);
|
||||
}
|
||||
|
||||
tagExists(tag) {
|
||||
return this.exec(`git show-ref --tags --quiet --verify -- refs/tags/${tag}`, { options }).then(
|
||||
() => true,
|
||||
() => false
|
||||
);
|
||||
}
|
||||
|
||||
isWorkingDirClean() {
|
||||
return this.exec('git diff --quiet HEAD', { options }).then(
|
||||
() => true,
|
||||
() => false
|
||||
);
|
||||
}
|
||||
|
||||
async getCommitsSinceLatestTag(commitsPath = '') {
|
||||
const latestTagName = await this.getLatestTagName();
|
||||
const ref = latestTagName ? `${latestTagName}..HEAD` : 'HEAD';
|
||||
return this.exec(`git rev-list ${ref} --count ${commitsPath ? `-- ${commitsPath}` : ''}`, { options }).then(Number);
|
||||
}
|
||||
|
||||
async getUpstreamArgs(pushRepo) {
|
||||
if (pushRepo && !this.isRemoteName(pushRepo)) {
|
||||
// Use (only) `pushRepo` if it's configured and looks like a url
|
||||
return [pushRepo];
|
||||
} else if (!(await this.hasUpstreamBranch())) {
|
||||
// Start tracking upstream branch (`pushRepo` is a name if set)
|
||||
return ['--set-upstream', pushRepo || 'origin', await this.getBranchName()];
|
||||
} else if (pushRepo && !invalidPushRepoRe.test(pushRepo)) {
|
||||
return [pushRepo];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
stage(file) {
|
||||
if (!file || !file.length) return noop;
|
||||
const files = castArray(file);
|
||||
return this.exec(['git', 'add', ...files]).catch(err => {
|
||||
this.log.warn(`Could not stage ${files}`);
|
||||
this.debug(err);
|
||||
});
|
||||
}
|
||||
|
||||
stageDir({ baseDir = '.' } = {}) {
|
||||
const { addUntrackedFiles } = this.options;
|
||||
return this.exec(['git', 'add', baseDir, addUntrackedFiles ? '--all' : '--update']);
|
||||
}
|
||||
|
||||
reset(file) {
|
||||
const files = castArray(file);
|
||||
return this.exec(['git', 'checkout', 'HEAD', '--', ...files]).catch(err => {
|
||||
this.log.warn(`Could not reset ${files}`);
|
||||
this.debug(err);
|
||||
});
|
||||
}
|
||||
|
||||
status() {
|
||||
return this.exec('git status --short --untracked-files=no', { options }).catch(() => null);
|
||||
}
|
||||
|
||||
commit({ message = this.options.commitMessage, args = this.options.commitArgs } = {}) {
|
||||
const msg = format(message, this.config.getContext());
|
||||
const commitMessageArgs = msg ? ['--message', msg] : [];
|
||||
return this.exec(['git', 'commit', ...commitMessageArgs, ...fixArgs(args)]).then(
|
||||
() => this.setContext({ isCommitted: true }),
|
||||
err => {
|
||||
this.debug(err);
|
||||
if (/nothing (added )?to commit/.test(err) || /nichts zu committen/.test(err)) {
|
||||
this.log.warn('No changes to commit. The latest commit will be tagged.');
|
||||
} else {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
tag({ name, annotation = this.options.tagAnnotation, args = this.options.tagArgs } = {}) {
|
||||
const message = format(annotation, this.config.getContext());
|
||||
const tagName = name || this.config.getContext('tagName');
|
||||
return this.exec(['git', 'tag', '--annotate', '--message', message, ...fixArgs(args), tagName])
|
||||
.then(() => this.setContext({ isTagged: true }))
|
||||
.catch(err => {
|
||||
const { latestTag, tagName } = this.config.getContext();
|
||||
if (/tag '.+' already exists/.test(err) && latestTag === tagName) {
|
||||
this.log.warn(`Tag "${tagName}" already exists`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async push({ args = this.options.pushArgs } = {}) {
|
||||
const { pushRepo } = this.options;
|
||||
const upstreamArgs = await this.getUpstreamArgs(pushRepo);
|
||||
try {
|
||||
const push = await this.exec(['git', 'push', ...fixArgs(args), ...upstreamArgs]);
|
||||
this.disableRollback();
|
||||
return push;
|
||||
} catch (error) {
|
||||
try {
|
||||
await this.rollbackTagPush();
|
||||
} catch (tagError) {
|
||||
this.log.warn(`An error was encountered when trying to rollback the tag on the remote: ${tagError.message}`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async rollbackTagPush() {
|
||||
const { isTagged } = this.getContext();
|
||||
if (isTagged) {
|
||||
const { tagName } = this.config.getContext();
|
||||
this.log.info(`Rolling back remote tag push ${tagName}`);
|
||||
await this.exec(`git push origin --delete ${tagName}`);
|
||||
}
|
||||
}
|
||||
|
||||
afterRelease() {
|
||||
this.disableRollback();
|
||||
}
|
||||
}
|
||||
|
||||
export default Git;
|
||||
19
node_modules/release-it/lib/plugin/git/prompts.js
generated
vendored
Normal file
19
node_modules/release-it/lib/plugin/git/prompts.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { format, truncateLines } from '../../util.js';
|
||||
|
||||
export default {
|
||||
commit: {
|
||||
type: 'confirm',
|
||||
message: context => `Commit (${truncateLines(format(context.git.commitMessage, context), 1, ' [...]')})?`,
|
||||
default: true
|
||||
},
|
||||
tag: {
|
||||
type: 'confirm',
|
||||
message: context => `Tag (${format(context.tagName, context)})?`,
|
||||
default: true
|
||||
},
|
||||
push: {
|
||||
type: 'confirm',
|
||||
message: () => 'Push?',
|
||||
default: true
|
||||
}
|
||||
};
|
||||
471
node_modules/release-it/lib/plugin/github/GitHub.js
generated
vendored
Normal file
471
node_modules/release-it/lib/plugin/github/GitHub.js
generated
vendored
Normal file
@@ -0,0 +1,471 @@
|
||||
import { createReadStream, statSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import open from 'open';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { glob } from 'tinyglobby';
|
||||
import mime from 'mime-types';
|
||||
import retry from 'async-retry';
|
||||
import newGithubReleaseUrl from 'new-github-release-url';
|
||||
import { ProxyAgent } from 'proxy-agent';
|
||||
import { format, parseVersion, readJSON, e, castArray } from '../../util.js';
|
||||
import Release from '../GitRelease.js';
|
||||
import prompts from './prompts.js';
|
||||
import { getCommitsFromChangelog, getResolvedIssuesFromChangelog, searchQueries } from './util.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@octokit/rest').RestEndpointMethodTypes['repos']['createRelease']['parameters']} CreateReleaseOptions
|
||||
*/
|
||||
|
||||
const pkg = readJSON(new URL('../../../package.json', import.meta.url));
|
||||
|
||||
const docs = 'https://git.io/release-it-github';
|
||||
|
||||
const RETRY_CODES = [408, 413, 429, 500, 502, 503, 504, 521, 522, 524];
|
||||
|
||||
const DEFAULT_RETRY_MIN_TIMEOUT = 1000;
|
||||
|
||||
const parseErrormsg = err => {
|
||||
let msg = err;
|
||||
if (err instanceof Error) {
|
||||
const { status, message } = err;
|
||||
const headers = err.response ? err.response.headers : {};
|
||||
msg = `${headers?.status || status} (${message})`;
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
const truncateBody = body => {
|
||||
// https://github.com/release-it/release-it/issues/965
|
||||
if (body && body.length >= 124000) return body.substring(0, 124000) + '...';
|
||||
return body;
|
||||
};
|
||||
|
||||
class GitHub extends Release {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.registerPrompts(prompts);
|
||||
}
|
||||
|
||||
async init() {
|
||||
await super.init();
|
||||
|
||||
const { skipChecks, tokenRef, web, update, assets } = this.options;
|
||||
|
||||
if (!this.token || web) {
|
||||
if (!web) {
|
||||
this.log.warn(`Environment variable "${tokenRef}" is required for automated GitHub Releases.`);
|
||||
this.log.warn('Falling back to web-based GitHub Release.');
|
||||
}
|
||||
this.setContext({ isWeb: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (web && assets) {
|
||||
this.log.warn('Assets are not included in web-based releases.');
|
||||
}
|
||||
|
||||
if (!skipChecks) {
|
||||
// If we're running on GitHub Actions, we can skip the authentication and
|
||||
// collaborator checks. Ref: https://bit.ly/2vsyRzu
|
||||
if (process.env.GITHUB_ACTIONS) {
|
||||
this.setContext({ username: process.env.GITHUB_ACTOR });
|
||||
} else {
|
||||
if (!(await this.isAuthenticated())) {
|
||||
throw e(`Could not authenticate with GitHub using environment variable "${tokenRef}".`, docs);
|
||||
}
|
||||
|
||||
if (!(await this.isCollaborator())) {
|
||||
const { repository } = this.getContext('repo');
|
||||
const { username } = this.getContext();
|
||||
throw e(`User ${username} is not a collaborator for ${repository}.`, docs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (update) {
|
||||
const { latestTag } = this.config.getContext();
|
||||
try {
|
||||
const { id, upload_url, tag_name } = await this.getLatestRelease();
|
||||
if (latestTag === tag_name) {
|
||||
this.setContext({ isUpdate: true, isReleased: true, releaseId: id, upload_url });
|
||||
} else {
|
||||
this.setContext({ isUpdate: false });
|
||||
}
|
||||
} catch (error) {
|
||||
this.setContext({ isUpdate: false });
|
||||
}
|
||||
if (!this.getContext('isUpdate')) {
|
||||
this.log.warn(`GitHub release for tag ${latestTag} was not found. Creating new release.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async isAuthenticated() {
|
||||
if (this.config.isDryRun) return true;
|
||||
try {
|
||||
this.log.verbose('octokit users#getAuthenticated');
|
||||
const { data } = await this.client.users.getAuthenticated();
|
||||
this.setContext({ username: data.login });
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async isCollaborator() {
|
||||
if (this.config.isDryRun) return true;
|
||||
const { owner, project: repo } = this.getContext('repo');
|
||||
const { username } = this.getContext();
|
||||
try {
|
||||
const options = { owner, repo, username };
|
||||
this.log.verbose(`octokit repos#checkCollaborator (${username})`);
|
||||
await this.client.repos.checkCollaborator(options);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async release() {
|
||||
const { assets } = this.options;
|
||||
const { isWeb, isUpdate } = this.getContext();
|
||||
const { isCI } = this.config;
|
||||
|
||||
const type = isUpdate ? 'update' : 'create';
|
||||
const publishMethod = `${type}Release`;
|
||||
|
||||
if (isWeb) {
|
||||
const task = () => this.createWebRelease();
|
||||
return this.step({ task, label: 'Generating link to GitHub Release web interface', prompt: 'release' });
|
||||
} else if (isCI) {
|
||||
await this.step({ task: () => this[publishMethod](), label: `GitHub ${type} release` });
|
||||
await this.step({ enabled: assets, task: () => this.uploadAssets(), label: 'GitHub upload assets' });
|
||||
return this.step({
|
||||
task: () => (isUpdate ? Promise.resolve() : this.commentOnResolvedItems()),
|
||||
label: 'GitHub comment on resolved items'
|
||||
});
|
||||
} else {
|
||||
const release = async () => {
|
||||
await this[publishMethod]();
|
||||
await this.uploadAssets();
|
||||
return isUpdate ? Promise.resolve(true) : this.commentOnResolvedItems();
|
||||
};
|
||||
return this.step({ task: release, label: `GitHub ${type} release`, prompt: 'release' });
|
||||
}
|
||||
}
|
||||
|
||||
handleError(err, bail) {
|
||||
const message = parseErrormsg(err);
|
||||
const githubError = new Error(message);
|
||||
this.log.verbose(err.errors);
|
||||
this.debug(err);
|
||||
if (!RETRY_CODES.includes(err.status)) {
|
||||
return bail(githubError);
|
||||
}
|
||||
throw githubError;
|
||||
}
|
||||
|
||||
get client() {
|
||||
if (this._client) return this._client;
|
||||
const { proxy, timeout } = this.options;
|
||||
const repo = this.getContext('repo');
|
||||
const host = this.options.host || repo.host;
|
||||
const isGitHub = host === 'github.com';
|
||||
const baseUrl = `https://${isGitHub ? 'api.github.com' : host}${isGitHub ? '' : '/api/v3'}`;
|
||||
const options = {
|
||||
baseUrl,
|
||||
auth: `token ${this.token}`,
|
||||
userAgent: `release-it/${pkg.version}`,
|
||||
log: this.config.isDebug ? console : {},
|
||||
request: {
|
||||
timeout
|
||||
}
|
||||
};
|
||||
|
||||
if (proxy) {
|
||||
options.request.agent = new ProxyAgent(proxy);
|
||||
}
|
||||
|
||||
const client = new Octokit(options);
|
||||
|
||||
this._client = client;
|
||||
return client;
|
||||
}
|
||||
|
||||
async getLatestRelease() {
|
||||
const { owner, project: repo } = this.getContext('repo');
|
||||
try {
|
||||
const options = { owner, repo };
|
||||
this.debug(options);
|
||||
const response = await this.client.repos.listReleases({ owner, repo, per_page: 1, page: 1 });
|
||||
this.debug(response.data[0]);
|
||||
return response.data[0];
|
||||
} catch (err) {
|
||||
return this.handleError(err, () => {});
|
||||
}
|
||||
}
|
||||
|
||||
async getOctokitReleaseOptions(options = {}) {
|
||||
const { owner, project: repo } = this.getContext('repo');
|
||||
const {
|
||||
releaseName,
|
||||
draft = false,
|
||||
preRelease = false,
|
||||
autoGenerate = false,
|
||||
makeLatest = true,
|
||||
discussionCategoryName = undefined
|
||||
} = this.options;
|
||||
const { tagName } = this.config.getContext();
|
||||
const { version, releaseNotes, isUpdate } = this.getContext();
|
||||
const { isPreRelease } = parseVersion(version);
|
||||
const name = format(releaseName, this.config.getContext());
|
||||
const releaseNotesObject = this.options.releaseNotes;
|
||||
|
||||
const body = autoGenerate
|
||||
? isUpdate
|
||||
? null
|
||||
: ''
|
||||
: truncateBody(releaseNotesObject?.commit ? await this.renderReleaseNotes(releaseNotesObject) : releaseNotes);
|
||||
|
||||
/**
|
||||
* @type {CreateReleaseOptions}
|
||||
*/
|
||||
const contextOptions = {
|
||||
owner,
|
||||
make_latest: makeLatest.toString(),
|
||||
repo,
|
||||
tag_name: tagName,
|
||||
name,
|
||||
body,
|
||||
draft,
|
||||
prerelease: isPreRelease || preRelease,
|
||||
generate_release_notes: autoGenerate,
|
||||
discussion_category_name: discussionCategoryName
|
||||
};
|
||||
return Object.assign(options, contextOptions);
|
||||
}
|
||||
|
||||
retry(fn) {
|
||||
const { retryMinTimeout } = this.options;
|
||||
return retry(fn, {
|
||||
retries: 2,
|
||||
minTimeout: typeof retryMinTimeout === 'number' ? retryMinTimeout : DEFAULT_RETRY_MIN_TIMEOUT
|
||||
});
|
||||
}
|
||||
|
||||
async createRelease() {
|
||||
const options = await this.getOctokitReleaseOptions();
|
||||
const { isDryRun } = this.config;
|
||||
|
||||
this.log.exec(`octokit repos.createRelease "${options.name}" (${options.tag_name})`, { isDryRun });
|
||||
|
||||
if (isDryRun) {
|
||||
this.setContext({ isReleased: true, releaseUrl: this.getReleaseUrlFallback(options.tag_name) });
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.retry(async bail => {
|
||||
try {
|
||||
this.debug(options);
|
||||
const response = await this.client.repos.createRelease(options);
|
||||
this.debug(response.data);
|
||||
const { html_url, upload_url, id, discussion_url } = response.data;
|
||||
this.setContext({
|
||||
isReleased: true,
|
||||
releaseId: id,
|
||||
releaseUrl: html_url,
|
||||
upload_url,
|
||||
discussionUrl: discussion_url
|
||||
});
|
||||
this.config.setContext({
|
||||
isReleased: true,
|
||||
releaseId: id,
|
||||
releaseUrl: html_url,
|
||||
upload_url,
|
||||
discussionUrl: discussion_url
|
||||
});
|
||||
this.log.verbose(`octokit repos.createRelease: done (${response.headers.location})`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return this.handleError(err, bail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uploadAsset(filePath) {
|
||||
const url = this.getContext('upload_url');
|
||||
const name = path.basename(filePath);
|
||||
const contentType = mime.contentType(name) || 'application/octet-stream';
|
||||
const contentLength = statSync(filePath).size;
|
||||
|
||||
return this.retry(async bail => {
|
||||
try {
|
||||
const options = {
|
||||
url,
|
||||
data: createReadStream(filePath),
|
||||
name,
|
||||
headers: {
|
||||
'content-type': contentType,
|
||||
'content-length': contentLength
|
||||
}
|
||||
};
|
||||
this.debug(options);
|
||||
const response = await this.client.repos.uploadReleaseAsset(options);
|
||||
this.debug(response.data);
|
||||
this.log.verbose(`octokit repos.uploadReleaseAsset: done (${response.data.browser_download_url})`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return this.handleError(err, bail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uploadAssets() {
|
||||
const { assets } = this.options;
|
||||
const { isReleased } = this.getContext();
|
||||
const context = this.config.getContext();
|
||||
const { isDryRun } = this.config;
|
||||
|
||||
const patterns = castArray(assets).map(pattern => format(pattern, context));
|
||||
|
||||
this.log.exec('octokit repos.uploadReleaseAssets', patterns, { isDryRun });
|
||||
|
||||
if (!assets || !isReleased) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return glob(patterns).then(files => {
|
||||
if (!files.length) {
|
||||
this.log.warn(`octokit repos.uploadReleaseAssets: did not find "${assets}" relative to ${process.cwd()}`);
|
||||
}
|
||||
|
||||
if (isDryRun) return Promise.resolve();
|
||||
|
||||
return Promise.all(files.map(filePath => this.uploadAsset(filePath)));
|
||||
});
|
||||
}
|
||||
|
||||
getReleaseUrlFallback(tagName) {
|
||||
const { host, repository } = this.getContext('repo');
|
||||
return `https://${host}/${repository}/releases/tag/${tagName}`;
|
||||
}
|
||||
|
||||
async generateWebUrl() {
|
||||
const repo = this.getContext('repo');
|
||||
const host = this.options.host || repo.host;
|
||||
const isGitHub = host === 'github.com';
|
||||
const options = await this.getOctokitReleaseOptions();
|
||||
const url = newGithubReleaseUrl({
|
||||
user: options.owner,
|
||||
repo: options.repo,
|
||||
tag: options.tag_name,
|
||||
isPrerelease: options.prerelease,
|
||||
title: options.name,
|
||||
body: options.body
|
||||
});
|
||||
return isGitHub ? url : url.replace('github.com', host);
|
||||
}
|
||||
|
||||
async createWebRelease() {
|
||||
const { isCI } = this.config;
|
||||
const { tagName } = this.config.getContext();
|
||||
const url = await this.generateWebUrl();
|
||||
if (isCI) {
|
||||
this.setContext({ isReleased: true, releaseUrl: url });
|
||||
} else {
|
||||
const isWindows = process.platform === 'win32';
|
||||
if (isWindows) this.log.info(`Opening ${url}`);
|
||||
await open(url, { wait: isWindows });
|
||||
this.setContext({ isReleased: true, releaseUrl: this.getReleaseUrlFallback(tagName) });
|
||||
}
|
||||
}
|
||||
|
||||
async updateRelease() {
|
||||
const { isDryRun } = this.config;
|
||||
const release_id = this.getContext('releaseId');
|
||||
const options = await this.getOctokitReleaseOptions({ release_id });
|
||||
|
||||
this.log.exec(`octokit repos.updateRelease (${options.tag_name})`, { isDryRun });
|
||||
|
||||
if (isDryRun) return true;
|
||||
|
||||
return this.retry(async bail => {
|
||||
try {
|
||||
this.debug(options);
|
||||
const response = await this.client.repos.updateRelease(options);
|
||||
this.setContext({ releaseUrl: response.data.html_url });
|
||||
this.debug(response.data);
|
||||
this.log.verbose(`octokit repos.updateRelease: done (${response.headers.location})`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return this.handleError(err, bail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async commentOnResolvedItems() {
|
||||
const { isDryRun } = this.config;
|
||||
const { host, owner, project: repo } = this.getContext('repo');
|
||||
const changelog = await this.getChangelog();
|
||||
const { comments } = this.options;
|
||||
const { submit, issue, pr } = comments ?? {};
|
||||
const context = this.getContext();
|
||||
|
||||
if (!submit || !changelog) return true;
|
||||
|
||||
const shas = getCommitsFromChangelog(changelog);
|
||||
const searchResults = await Promise.all(searchQueries(this.client, owner, repo, shas));
|
||||
const mergedPullRequests = searchResults.flatMap(items => items.map(item => ({ type: 'pr', number: item.number })));
|
||||
|
||||
const hostURL = 'https://' + (this.options.host || host);
|
||||
const resolvedIssues = getResolvedIssuesFromChangelog(hostURL, owner, repo, changelog);
|
||||
|
||||
for (const item of [...resolvedIssues, ...mergedPullRequests]) {
|
||||
const { type, number } = item;
|
||||
const comment = format(format(type === 'pr' ? pr : issue, context), context);
|
||||
const url = `${hostURL}/${owner}/${repo}/${type === 'pr' ? 'pull' : 'issues'}/${number}`;
|
||||
|
||||
if (isDryRun) {
|
||||
this.log.exec(`octokit issues.createComment (${url})`, { isDryRun });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.issues.createComment({ owner, repo, issue_number: number, body: comment });
|
||||
this.log.log(`● Commented on ${url}`);
|
||||
} catch (error) {
|
||||
this.log.log(`✕ Failed to comment on ${url}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getCommits() {
|
||||
const { owner, project: repo } = this.getContext('repo');
|
||||
const { latestTag } = this.config.getContext();
|
||||
this.debug({ owner, repo, base: latestTag, head: 'HEAD' });
|
||||
const { data } = await this.client.repos.compareCommits({ owner, repo, base: latestTag, head: 'HEAD' });
|
||||
return data.commits;
|
||||
}
|
||||
|
||||
async renderReleaseNotes(releaseNotes) {
|
||||
const { commit: template, excludeMatches = [] } = releaseNotes;
|
||||
const commits = await this.getCommits();
|
||||
|
||||
if (this.options.commit) commits.pop();
|
||||
|
||||
return commits
|
||||
.map(commit => {
|
||||
commit.commit.subject = commit.commit.message.split('\n')[0];
|
||||
const partial = template.replace(/(?<!\$)\{((?:[^{}]|\${[^}]+})+)\}/g, (_, block) => {
|
||||
const rendered = format(block, commit);
|
||||
return excludeMatches.some(match => rendered.includes(match)) ? '' : rendered;
|
||||
});
|
||||
return format(partial, commit);
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
export default GitHub;
|
||||
16
node_modules/release-it/lib/plugin/github/prompts.js
generated
vendored
Normal file
16
node_modules/release-it/lib/plugin/github/prompts.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { format } from '../../util.js';
|
||||
|
||||
const message = context => {
|
||||
const { isPreRelease, github } = context;
|
||||
const { releaseName, update } = github;
|
||||
const name = format(releaseName, context);
|
||||
return `${update ? 'Update' : 'Create a'} ${isPreRelease ? 'pre-' : ''}release on GitHub (${name})?`;
|
||||
};
|
||||
|
||||
export default {
|
||||
release: {
|
||||
type: 'confirm',
|
||||
message,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
42
node_modules/release-it/lib/plugin/github/util.js
generated
vendored
Normal file
42
node_modules/release-it/lib/plugin/github/util.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Totally much borrowed from https://github.com/semantic-release/github/blob/master/lib/success.js
|
||||
import issueParser from 'issue-parser';
|
||||
|
||||
/** @internal */
|
||||
export const getSearchQueries = (base, commits, separator = '+') => {
|
||||
const encodedSeparator = encodeURIComponent(separator);
|
||||
|
||||
return commits.reduce((searches, commit) => {
|
||||
const lastSearch = searches[searches.length - 1];
|
||||
if (lastSearch && encodeURIComponent(lastSearch).length + commit.length <= 256 - encodedSeparator.length) {
|
||||
searches[searches.length - 1] = `${lastSearch}${separator}${commit}`;
|
||||
} else {
|
||||
searches.push(`${base}${separator}${commit}`);
|
||||
}
|
||||
|
||||
return searches;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const searchQueries = (client, owner, repo, shas) =>
|
||||
getSearchQueries(`repo:${owner}/${repo}+type:pr+is:merged`, shas).map(
|
||||
async q => (await client.search.issuesAndPullRequests({ q, advanced_search: true })).data.items
|
||||
);
|
||||
|
||||
export const getCommitsFromChangelog = changelog => {
|
||||
const regex = /\(([a-f0-9]{7,})\)/i;
|
||||
return changelog.split('\n').flatMap(message => {
|
||||
const match = message.match(regex);
|
||||
if (match) return match[1];
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
export const getResolvedIssuesFromChangelog = (host, owner, repo, changelog) => {
|
||||
const parser = issueParser('github', { hosts: [host] });
|
||||
return changelog
|
||||
.split('\n')
|
||||
.map(parser)
|
||||
.flatMap(parsed => parsed.actions.close)
|
||||
.filter(action => !action.slug || action.slug === `${owner}/${repo}`)
|
||||
.map(action => ({ type: 'issue', number: parseInt(action.issue, 10) }));
|
||||
};
|
||||
338
node_modules/release-it/lib/plugin/gitlab/GitLab.js
generated
vendored
Normal file
338
node_modules/release-it/lib/plugin/gitlab/GitLab.js
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs'; // import fs here so it can be stubbed in tests
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { Agent } from 'undici';
|
||||
import Release from '../GitRelease.js';
|
||||
import { format, e, castArray } from '../../util.js';
|
||||
import prompts from './prompts.js';
|
||||
|
||||
const docs = 'https://git.io/release-it-gitlab';
|
||||
|
||||
const noop = Promise.resolve();
|
||||
|
||||
class GitLab extends Release {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.registerPrompts(prompts);
|
||||
this.assets = [];
|
||||
const { secure } = this.options;
|
||||
const certificateAuthorityFileRef = this.options.certificateAuthorityFileRef || 'CI_SERVER_TLS_CA_FILE';
|
||||
const certificateAuthorityFile =
|
||||
this.options.certificateAuthorityFile || process.env[certificateAuthorityFileRef] || null;
|
||||
this.certificateAuthorityOption = {};
|
||||
|
||||
const needsCustomAgent = Boolean(secure === false || certificateAuthorityFile);
|
||||
|
||||
if (needsCustomAgent) {
|
||||
this.certificateAuthorityOption.dispatcher = new Agent({
|
||||
connect: {
|
||||
rejectUnauthorized: secure,
|
||||
ca: certificateAuthorityFile ? fs.readFileSync(certificateAuthorityFile) : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
await super.init();
|
||||
|
||||
const { skipChecks, tokenRef, tokenHeader } = this.options;
|
||||
const { repo } = this.getContext();
|
||||
const hasJobToken = (tokenHeader || '').toLowerCase() === 'job-token';
|
||||
const origin = this.options.origin || `https://${repo.host}`;
|
||||
this.setContext({
|
||||
id: encodeURIComponent(repo.repository),
|
||||
origin,
|
||||
baseUrl: `${origin}/api/v4`
|
||||
});
|
||||
|
||||
if (skipChecks) return;
|
||||
|
||||
if (!this.token) {
|
||||
throw e(`Environment variable "${tokenRef}" is required for GitLab releases.`, docs);
|
||||
}
|
||||
|
||||
if (!hasJobToken) {
|
||||
if (!(await this.isAuthenticated())) {
|
||||
throw e(`Could not authenticate with GitLab using environment variable "${tokenRef}".`, docs);
|
||||
}
|
||||
if (!(await this.isCollaborator())) {
|
||||
const { user, repo } = this.getContext();
|
||||
throw e(`User ${user.username} is not a collaborator for ${repo.repository}.`, docs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async isAuthenticated() {
|
||||
if (this.config.isDryRun) return true;
|
||||
const endpoint = `user`;
|
||||
try {
|
||||
const { id, username } = await this.request(endpoint, { method: 'GET' });
|
||||
this.setContext({ user: { id, username } });
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async isCollaborator() {
|
||||
if (this.config.isDryRun) return true;
|
||||
const { id, user } = this.getContext();
|
||||
const endpoint = `projects/${id}/members/all/${user.id}`;
|
||||
try {
|
||||
const { access_level } = await this.request(endpoint, { method: 'GET' });
|
||||
return access_level && access_level >= 30;
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async beforeRelease() {
|
||||
await super.beforeRelease();
|
||||
await this.checkReleaseMilestones();
|
||||
}
|
||||
|
||||
async checkReleaseMilestones() {
|
||||
if (this.options.skipChecks) return;
|
||||
|
||||
const releaseMilestones = this.getReleaseMilestones();
|
||||
if (releaseMilestones.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.exec(`gitlab releases#checkReleaseMilestones`);
|
||||
|
||||
const { id } = this.getContext();
|
||||
const endpoint = `projects/${id}/milestones`;
|
||||
const requests = releaseMilestones.map(milestone => {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
searchParams: {
|
||||
title: milestone,
|
||||
include_parent_milestones: true
|
||||
}
|
||||
};
|
||||
return this.request(endpoint, options).then(response => {
|
||||
if (!Array.isArray(response)) {
|
||||
const { baseUrl } = this.getContext();
|
||||
throw new Error(
|
||||
`Unexpected response from ${baseUrl}/${endpoint}. Expected an array but got: ${JSON.stringify(response)}`
|
||||
);
|
||||
}
|
||||
if (response.length === 0) {
|
||||
const error = new Error(`Milestone '${milestone}' does not exist.`);
|
||||
this.log.warn(error.message);
|
||||
throw error;
|
||||
}
|
||||
this.log.verbose(`gitlab releases#checkReleaseMilestones: milestone '${milestone}' exists`);
|
||||
});
|
||||
});
|
||||
try {
|
||||
await Promise.allSettled(requests).then(results => {
|
||||
for (const result of results) {
|
||||
if (result.status === 'rejected') {
|
||||
throw e('Missing one or more milestones in GitLab. Creating a GitLab release will fail.', docs);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
throw err;
|
||||
}
|
||||
this.log.verbose('gitlab releases#checkReleaseMilestones: done');
|
||||
}
|
||||
|
||||
getReleaseMilestones() {
|
||||
const { milestones } = this.options;
|
||||
return (milestones || []).map(milestone => format(milestone, this.config.getContext()));
|
||||
}
|
||||
|
||||
async release() {
|
||||
const glRelease = () => this.createRelease();
|
||||
const glUploadAssets = () => this.uploadAssets();
|
||||
|
||||
if (this.config.isCI) {
|
||||
await this.step({ enabled: this.options.assets, task: glUploadAssets, label: 'GitLab upload assets' });
|
||||
return await this.step({ task: glRelease, label: 'GitLab release' });
|
||||
} else {
|
||||
const release = () => glUploadAssets().then(() => glRelease());
|
||||
return await this.step({ task: release, label: 'GitLab release', prompt: 'release' });
|
||||
}
|
||||
}
|
||||
|
||||
async request(endpoint, options) {
|
||||
const { baseUrl } = this.getContext();
|
||||
this.debug(Object.assign({ url: `${baseUrl}/${endpoint}` }, options));
|
||||
const method = (options.method || 'POST').toLowerCase();
|
||||
const { tokenHeader } = this.options;
|
||||
const url = `${baseUrl}/${endpoint}${options.searchParams ? `?${new URLSearchParams(options.searchParams)}` : ''}`;
|
||||
const headers = {
|
||||
'user-agent': 'webpro/release-it',
|
||||
[tokenHeader]: this.token
|
||||
};
|
||||
// When using fetch() with FormData bodies, we should not set the Content-Type header.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects#sending_files_using_a_formdata_object
|
||||
if (!(options.body instanceof FormData)) {
|
||||
headers['Content-Type'] = typeof options.json !== 'undefined' ? 'application/json' : 'text/plain';
|
||||
}
|
||||
const requestOptions = {
|
||||
method,
|
||||
headers,
|
||||
...this.certificateAuthorityOption
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
url,
|
||||
options.json || options.body
|
||||
? {
|
||||
...requestOptions,
|
||||
body: options.json ? JSON.stringify(options.json) : options.body
|
||||
}
|
||||
: requestOptions
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.json();
|
||||
throw new Error(body.error);
|
||||
}
|
||||
|
||||
const body = await response.json();
|
||||
this.debug(body);
|
||||
return body;
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async createRelease() {
|
||||
const { releaseName } = this.options;
|
||||
const { tagName, branchName, git: { tagAnnotation } = {} } = this.config.getContext();
|
||||
const { id, releaseNotes, repo, origin } = this.getContext();
|
||||
const { isDryRun } = this.config;
|
||||
const name = format(releaseName, this.config.getContext());
|
||||
const tagMessage = format(tagAnnotation, this.config.getContext());
|
||||
const description = releaseNotes || '-';
|
||||
const releaseUrl = `${origin}/${repo.repository}/-/releases/${tagName}`;
|
||||
const releaseMilestones = this.getReleaseMilestones();
|
||||
|
||||
this.log.exec(`gitlab releases#createRelease "${name}" (${tagName})`, { isDryRun });
|
||||
|
||||
if (isDryRun) {
|
||||
this.setContext({ isReleased: true, releaseUrl });
|
||||
return true;
|
||||
}
|
||||
|
||||
const endpoint = `projects/${id}/releases`;
|
||||
const options = {
|
||||
json: {
|
||||
name,
|
||||
ref: branchName,
|
||||
tag_name: tagName,
|
||||
tag_message: tagMessage,
|
||||
description
|
||||
}
|
||||
};
|
||||
|
||||
if (this.assets.length) {
|
||||
options.json.assets = {
|
||||
links: this.assets
|
||||
};
|
||||
}
|
||||
|
||||
if (releaseMilestones.length) {
|
||||
options.json.milestones = releaseMilestones;
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await this.request(endpoint, options);
|
||||
const releaseUrlSelf = body._links?.self ?? releaseUrl;
|
||||
this.log.verbose('gitlab releases#createRelease: done');
|
||||
this.setContext({ isReleased: true, releaseUrl: releaseUrlSelf });
|
||||
this.config.setContext({ isReleased: true, releaseUrl: releaseUrlSelf });
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async uploadAsset(filePath) {
|
||||
const name = path.basename(filePath);
|
||||
const { useIdsForUrls, useGenericPackageRepositoryForAssets, genericPackageRepositoryName } = this.options;
|
||||
const { id, origin, repo, version, baseUrl } = this.getContext();
|
||||
|
||||
const endpoint = useGenericPackageRepositoryForAssets
|
||||
? `projects/${id}/packages/generic/${genericPackageRepositoryName}/${version}/${name}`
|
||||
: `projects/${id}/uploads`;
|
||||
|
||||
if (useGenericPackageRepositoryForAssets) {
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
body: await readFile(filePath)
|
||||
};
|
||||
|
||||
try {
|
||||
const body = await this.request(endpoint, options);
|
||||
if (!(body.message && body.message == '201 Created')) {
|
||||
throw new Error(`GitLab asset upload failed`);
|
||||
}
|
||||
this.log.verbose(`gitlab releases#uploadAsset: done (${endpoint})`);
|
||||
this.assets.push({
|
||||
name,
|
||||
url: `${baseUrl}/${endpoint}`
|
||||
});
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
const body = new FormData();
|
||||
const rawData = await readFile(filePath);
|
||||
body.set('file', new Blob([rawData]), name);
|
||||
const options = { body };
|
||||
|
||||
try {
|
||||
const body = await this.request(endpoint, options);
|
||||
this.log.verbose(`gitlab releases#uploadAsset: done (${body.url})`);
|
||||
this.assets.push({
|
||||
name,
|
||||
url: useIdsForUrls ? `${origin}${body.full_path}` : `${origin}/${repo.repository}${body.url}`
|
||||
});
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadAssets() {
|
||||
const { assets } = this.options;
|
||||
const { isDryRun } = this.config;
|
||||
const context = this.config.getContext();
|
||||
|
||||
const patterns = castArray(assets).map(pattern => format(pattern, context));
|
||||
|
||||
this.log.exec('gitlab releases#uploadAssets', patterns, { isDryRun });
|
||||
|
||||
if (!assets) {
|
||||
return noop;
|
||||
}
|
||||
|
||||
return glob(patterns).then(files => {
|
||||
if (!files.length) {
|
||||
this.log.warn(`gitlab releases#uploadAssets: could not find "${assets}" relative to ${process.cwd()}`);
|
||||
}
|
||||
|
||||
if (isDryRun) return Promise.resolve();
|
||||
|
||||
return Promise.all(files.map(filePath => this.uploadAsset(filePath)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default GitLab;
|
||||
9
node_modules/release-it/lib/plugin/gitlab/prompts.js
generated
vendored
Normal file
9
node_modules/release-it/lib/plugin/gitlab/prompts.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { format } from '../../util.js';
|
||||
|
||||
export default {
|
||||
release: {
|
||||
type: 'confirm',
|
||||
message: context => `Create a release on GitLab (${format(context.gitlab.releaseName, context)})?`,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
284
node_modules/release-it/lib/plugin/npm/npm.js
generated
vendored
Normal file
284
node_modules/release-it/lib/plugin/npm/npm.js
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
import path from 'node:path';
|
||||
import semver from 'semver';
|
||||
import urlJoin from 'url-join';
|
||||
import Plugin from '../Plugin.js';
|
||||
import { hasAccess, rejectAfter, parseVersion, readJSON, e, fixArgs } from '../../util.js';
|
||||
import prompts from './prompts.js';
|
||||
|
||||
const docs = 'https://git.io/release-it-npm';
|
||||
|
||||
const options = { write: false };
|
||||
|
||||
const MANIFEST_PATH = './package.json';
|
||||
const DEFAULT_TAG = 'latest';
|
||||
const DEFAULT_TAG_PRERELEASE = 'next';
|
||||
const NPM_BASE_URL = 'https://www.npmjs.com';
|
||||
const NPM_PUBLIC_PATH = '/package';
|
||||
const DEFAULT_TIMEOUT = 10;
|
||||
|
||||
class npm extends Plugin {
|
||||
static isEnabled(options) {
|
||||
return hasAccess(MANIFEST_PATH) && options !== false;
|
||||
}
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.registerPrompts(prompts);
|
||||
}
|
||||
|
||||
async init() {
|
||||
const { name, version: latestVersion, private: isPrivate, publishConfig } = readJSON(path.resolve(MANIFEST_PATH));
|
||||
this.setContext({ name, latestVersion, private: isPrivate, publishConfig });
|
||||
this.config.setContext({ npm: { name } });
|
||||
|
||||
const { publish, skipChecks } = this.options;
|
||||
|
||||
const timeout = Number(this.options.timeout ?? DEFAULT_TIMEOUT) * 1000;
|
||||
|
||||
if (publish === false || isPrivate) return;
|
||||
|
||||
if (skipChecks) return;
|
||||
|
||||
const validations = Promise.all([this.isRegistryUp(), this.isAuthenticated(), this.getLatestRegistryVersion()]);
|
||||
|
||||
await Promise.race([validations, rejectAfter(timeout, e(`Timed out after ${timeout}ms.`, docs))]);
|
||||
|
||||
const [isRegistryUp, isAuthenticated, latestVersionInRegistry] = await validations;
|
||||
|
||||
if (!isRegistryUp) {
|
||||
throw e(`Unable to reach npm registry (timed out after ${timeout}ms).`, docs);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
throw e('Not authenticated with npm. Please `npm login` and try again.', docs);
|
||||
}
|
||||
|
||||
if (!(await this.isCollaborator())) {
|
||||
const { username } = this.getContext();
|
||||
throw e(`User ${username} is not a collaborator for ${name}.`, docs);
|
||||
}
|
||||
|
||||
if (!latestVersionInRegistry) {
|
||||
this.log.warn('No version found in npm registry. Assuming new package.');
|
||||
} else {
|
||||
if (!semver.eq(latestVersion, latestVersionInRegistry)) {
|
||||
this.log.warn(
|
||||
`Latest version in registry (${latestVersionInRegistry}) does not match package.json (${latestVersion}).`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.getContext('name');
|
||||
}
|
||||
|
||||
getLatestVersion() {
|
||||
return this.options.ignoreVersion ? null : this.getContext('latestVersion');
|
||||
}
|
||||
|
||||
async bump(version) {
|
||||
const tag = this.options.tag || (await this.resolveTag(version));
|
||||
this.setContext({ version, tag });
|
||||
|
||||
if (!this.config.isIncrement) return false;
|
||||
|
||||
const { versionArgs, allowSameVersion } = this.options;
|
||||
const args = [version, '--no-git-tag-version', allowSameVersion && '--allow-same-version', ...fixArgs(versionArgs)];
|
||||
const task = () => this.exec(`npm version ${args.filter(Boolean).join(' ')}`);
|
||||
return this.spinner.show({ task, label: 'npm version' });
|
||||
}
|
||||
|
||||
release() {
|
||||
if (this.options.publish === false) return false;
|
||||
if (this.getContext('private')) return false;
|
||||
const publish = () => this.publish({ otpCallback });
|
||||
const otpCallback =
|
||||
this.config.isCI && !this.config.isPromptOnlyVersion ? null : task => this.step({ prompt: 'otp', task });
|
||||
return this.step({ task: publish, label: 'npm publish', prompt: 'publish' });
|
||||
}
|
||||
|
||||
isRegistryUp() {
|
||||
const registry = this.getRegistry();
|
||||
const registryArg = registry ? ` --registry ${registry}` : '';
|
||||
return this.exec(`npm ping${registryArg}`, { options }).then(
|
||||
() => true,
|
||||
err => {
|
||||
if (/code E40[04]|404.*(ping not found|No content for path)/.test(err)) {
|
||||
this.log.warn('Ignoring response from unsupported `npm ping` command.');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
const registry = this.getRegistry();
|
||||
const registryArg = registry ? ` --registry ${registry}` : '';
|
||||
return this.exec(`npm whoami${registryArg}`, { options }).then(
|
||||
output => {
|
||||
const username = output ? output.trim() : null;
|
||||
this.setContext({ username });
|
||||
return true;
|
||||
},
|
||||
err => {
|
||||
this.debug(err);
|
||||
if (/code E40[04]/.test(err)) {
|
||||
this.log.warn('Ignoring response from unsupported `npm whoami` command.');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async isCollaborator() {
|
||||
const registry = this.getRegistry();
|
||||
const registryArg = registry ? ` --registry ${registry}` : '';
|
||||
const name = this.getName();
|
||||
const { username } = this.getContext();
|
||||
if (username === undefined) return true;
|
||||
if (username === null) return false;
|
||||
|
||||
try {
|
||||
let npmVersion = await this.exec('npm --version', { options });
|
||||
|
||||
let accessCommand;
|
||||
if (semver.gt(npmVersion, '9.0.0')) {
|
||||
accessCommand = 'npm access list collaborators --json';
|
||||
} else {
|
||||
accessCommand = 'npm access ls-collaborators';
|
||||
}
|
||||
|
||||
const output = await this.exec(`${accessCommand} ${name}${registryArg}`, { options });
|
||||
|
||||
try {
|
||||
const collaborators = JSON.parse(output);
|
||||
const permissions = collaborators[username];
|
||||
return permissions && permissions.includes('write');
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
if (/code E400/.test(err)) {
|
||||
this.log.warn('Ignoring response from unsupported `npm access` command.');
|
||||
} else {
|
||||
this.log.warn(`Unable to verify if user ${username} is a collaborator for ${name}.`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async getLatestRegistryVersion() {
|
||||
const registry = this.getRegistry();
|
||||
const registryArg = registry ? ` --registry ${registry}` : '';
|
||||
const name = this.getName();
|
||||
const latestVersion = this.getLatestVersion();
|
||||
const tag = await this.resolveTag(latestVersion);
|
||||
return this.exec(`npm show ${name}@${tag} version${registryArg}`, { options }).catch(() => null);
|
||||
}
|
||||
|
||||
getRegistryPreReleaseTags() {
|
||||
return this.exec(`npm view ${this.getName()} dist-tags --json`, { options }).then(
|
||||
output => {
|
||||
try {
|
||||
const tags = JSON.parse(output);
|
||||
return Object.keys(tags).filter(tag => tag !== DEFAULT_TAG);
|
||||
} catch (err) {
|
||||
this.debug(err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
|
||||
getPackageUrl() {
|
||||
const baseUrl = this.getRegistry() || NPM_BASE_URL;
|
||||
const publicPath = this.getPublicPath() || NPM_PUBLIC_PATH;
|
||||
return urlJoin(baseUrl, publicPath, this.getName());
|
||||
}
|
||||
|
||||
getRegistry() {
|
||||
const { publishConfig } = this.getContext();
|
||||
const registries = publishConfig
|
||||
? publishConfig.registry
|
||||
? [publishConfig.registry]
|
||||
: Object.keys(publishConfig)
|
||||
.filter(key => key.endsWith('registry'))
|
||||
.map(key => publishConfig[key])
|
||||
: [];
|
||||
return registries[0];
|
||||
}
|
||||
|
||||
getPublicPath() {
|
||||
const { publishConfig } = this.getContext();
|
||||
return (publishConfig && publishConfig.publicPath) ?? '';
|
||||
}
|
||||
|
||||
async guessPreReleaseTag() {
|
||||
const [tag] = await this.getRegistryPreReleaseTags();
|
||||
if (tag) {
|
||||
return tag;
|
||||
} else {
|
||||
this.log.warn(`Unable to get pre-release tag(s) from npm registry. Using "${DEFAULT_TAG_PRERELEASE}".`);
|
||||
return DEFAULT_TAG_PRERELEASE;
|
||||
}
|
||||
}
|
||||
|
||||
async resolveTag(version) {
|
||||
const { tag } = this.options;
|
||||
const { isPreRelease, preReleaseId } = parseVersion(version);
|
||||
if (!isPreRelease) {
|
||||
return DEFAULT_TAG;
|
||||
} else {
|
||||
return tag || preReleaseId || (await this.guessPreReleaseTag());
|
||||
}
|
||||
}
|
||||
|
||||
async publish({ otp = this.options.otp, otpCallback } = {}) {
|
||||
const { publishPath = '.', publishArgs } = this.options;
|
||||
const { private: isPrivate, tag = DEFAULT_TAG } = this.getContext();
|
||||
const otpArg = otp ? `--otp ${otp}` : '';
|
||||
const dryRunArg = this.config.isDryRun ? '--dry-run' : '';
|
||||
const registry = this.getRegistry();
|
||||
const registryArg = registry ? `--registry ${registry}` : '';
|
||||
if (isPrivate) {
|
||||
this.log.warn('Skip publish: package is private.');
|
||||
return false;
|
||||
}
|
||||
const args = [publishPath, `--tag ${tag}`, otpArg, dryRunArg, registryArg, ...fixArgs(publishArgs)].filter(Boolean);
|
||||
return this.exec(`npm publish ${args.join(' ')}`, { options })
|
||||
.then(() => {
|
||||
this.setContext({ isReleased: true });
|
||||
})
|
||||
.catch(err => {
|
||||
this.debug(err);
|
||||
if (this.config.isDryRun && /publish over the previously published version/.test(err)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (/one-time pass/.test(err)) {
|
||||
if (otp != null) {
|
||||
this.log.warn('The provided OTP is incorrect or has expired.');
|
||||
}
|
||||
if (otpCallback) {
|
||||
return otpCallback(otp => this.publish({ otp, otpCallback }));
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
afterRelease() {
|
||||
const { isReleased } = this.getContext();
|
||||
if (isReleased) {
|
||||
this.log.log(`🔗 ${this.getPackageUrl()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default npm;
|
||||
12
node_modules/release-it/lib/plugin/npm/prompts.js
generated
vendored
Normal file
12
node_modules/release-it/lib/plugin/npm/prompts.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export default {
|
||||
publish: {
|
||||
type: 'confirm',
|
||||
message: context =>
|
||||
`Publish ${context.npm.name}${context.npm.tag === 'latest' ? '' : `@${context.npm.tag}`} to npm?`,
|
||||
default: true
|
||||
},
|
||||
otp: {
|
||||
type: 'input',
|
||||
message: () => `Please enter OTP for npm:`
|
||||
}
|
||||
};
|
||||
131
node_modules/release-it/lib/plugin/version/Version.js
generated
vendored
Normal file
131
node_modules/release-it/lib/plugin/version/Version.js
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
import { styleText } from 'node:util';
|
||||
import semver from 'semver';
|
||||
import Plugin from '../Plugin.js';
|
||||
|
||||
const RELEASE_TYPES = ['patch', 'minor', 'major'];
|
||||
const PRERELEASE_TYPES = ['prepatch', 'preminor', 'premajor'];
|
||||
const CONTINUATION_TYPES = ['prerelease', 'pre'];
|
||||
const ALL_RELEASE_TYPES = [...RELEASE_TYPES, ...PRERELEASE_TYPES, ...CONTINUATION_TYPES];
|
||||
|
||||
const CHOICES = {
|
||||
latestIsPreRelease: [CONTINUATION_TYPES[0], ...RELEASE_TYPES],
|
||||
preRelease: PRERELEASE_TYPES,
|
||||
default: [...RELEASE_TYPES, ...PRERELEASE_TYPES]
|
||||
};
|
||||
|
||||
const getIncrementChoices = context => {
|
||||
const { latestIsPreRelease, isPreRelease, preReleaseId, preReleaseBase } = context.version;
|
||||
const types = latestIsPreRelease ? CHOICES.latestIsPreRelease : isPreRelease ? CHOICES.preRelease : CHOICES.default;
|
||||
const choices = types.map(increment => ({
|
||||
name: `${increment} (${semver.inc(context.latestVersion, increment, preReleaseId, preReleaseBase)})`,
|
||||
value: increment
|
||||
}));
|
||||
const otherChoice = {
|
||||
name: 'Other, please specify...',
|
||||
value: null
|
||||
};
|
||||
return [...choices, otherChoice];
|
||||
};
|
||||
|
||||
const versionTransformer = context => input =>
|
||||
semver.valid(input)
|
||||
? semver.gt(input, context.latestVersion)
|
||||
? styleText('green', input)
|
||||
: styleText('red', input)
|
||||
: styleText(['red', 'bold'], input);
|
||||
|
||||
const prompts = {
|
||||
incrementList: {
|
||||
type: 'list',
|
||||
message: () => 'Select increment (next version):',
|
||||
choices: context => getIncrementChoices(context),
|
||||
pageSize: 9
|
||||
},
|
||||
version: {
|
||||
type: 'input',
|
||||
message: () => `Please enter a valid version:`,
|
||||
transformer: context => versionTransformer(context),
|
||||
validate: input => !!semver.valid(input) || 'The version must follow the semver standard.'
|
||||
}
|
||||
};
|
||||
|
||||
class Version extends Plugin {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.registerPrompts(prompts);
|
||||
}
|
||||
|
||||
getIncrement(options) {
|
||||
return options.increment;
|
||||
}
|
||||
|
||||
getIncrementedVersionCI(options) {
|
||||
return this.incrementVersion(options);
|
||||
}
|
||||
|
||||
async getIncrementedVersion(options) {
|
||||
const { isCI } = this.config;
|
||||
const version = this.incrementVersion(options);
|
||||
return version || (isCI ? null : await this.promptIncrementVersion(options));
|
||||
}
|
||||
|
||||
promptIncrementVersion(options) {
|
||||
return new Promise(resolve => {
|
||||
this.step({
|
||||
prompt: 'incrementList',
|
||||
task: increment =>
|
||||
increment
|
||||
? resolve(this.incrementVersion(Object.assign({}, options, { increment })))
|
||||
: this.step({ prompt: 'version', task: resolve })
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isPreRelease(version) {
|
||||
return Boolean(semver.prerelease(version));
|
||||
}
|
||||
|
||||
isValid(version) {
|
||||
return Boolean(semver.valid(version));
|
||||
}
|
||||
|
||||
incrementVersion({ latestVersion, increment, isPreRelease, preReleaseId, preReleaseBase }) {
|
||||
if (increment === false) return latestVersion;
|
||||
|
||||
const latestIsPreRelease = this.isPreRelease(latestVersion);
|
||||
const isValidVersion = this.isValid(increment);
|
||||
|
||||
if (latestVersion) {
|
||||
this.setContext({ latestIsPreRelease });
|
||||
}
|
||||
|
||||
if (isValidVersion && semver.gte(increment, latestVersion)) {
|
||||
return increment;
|
||||
}
|
||||
|
||||
if (isPreRelease && !increment && latestIsPreRelease) {
|
||||
return semver.inc(latestVersion, 'prerelease', preReleaseId, preReleaseBase);
|
||||
}
|
||||
|
||||
if (this.config.isCI && !increment) {
|
||||
if (isPreRelease) {
|
||||
return semver.inc(latestVersion, 'prepatch', preReleaseId, preReleaseBase);
|
||||
} else {
|
||||
return semver.inc(latestVersion, 'patch');
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedType = RELEASE_TYPES.includes(increment) && isPreRelease ? `pre${increment}` : increment;
|
||||
if (ALL_RELEASE_TYPES.includes(normalizedType)) {
|
||||
return semver.inc(latestVersion, normalizedType, preReleaseId, preReleaseBase);
|
||||
}
|
||||
|
||||
const coercedVersion = !isValidVersion && semver.coerce(increment);
|
||||
if (coercedVersion) {
|
||||
this.log.warn(`Coerced invalid semver version "${increment}" into "${coercedVersion}".`);
|
||||
return coercedVersion.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Version;
|
||||
Reference in New Issue
Block a user