first commit
This commit is contained in:
64
node_modules/release-it/lib/args.js
generated
vendored
Normal file
64
node_modules/release-it/lib/args.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import parseArgs from 'yargs-parser';
|
||||
|
||||
const aliases = {
|
||||
c: 'config',
|
||||
d: 'dry-run',
|
||||
h: 'help',
|
||||
i: 'increment',
|
||||
v: 'version',
|
||||
V: 'verbose'
|
||||
};
|
||||
|
||||
const booleanOptions = [
|
||||
'dry-run',
|
||||
'ci',
|
||||
'git',
|
||||
'npm',
|
||||
'github',
|
||||
'gitlab',
|
||||
'git.addUntrackedFiles',
|
||||
'git.requireCleanWorkingDir',
|
||||
'git.requireUpstream',
|
||||
'git.requireCommits',
|
||||
'git.requireCommitsFail',
|
||||
'git.commit',
|
||||
'git.tag',
|
||||
'git.push',
|
||||
'git.getLatestTagFromAllRefs',
|
||||
'git.skipChecks',
|
||||
'github.release',
|
||||
'github.autoGenerate',
|
||||
'github.preRelease',
|
||||
'github.draft',
|
||||
'github.skipChecks',
|
||||
'github.web',
|
||||
'github.comments.submit',
|
||||
'gitlab.release',
|
||||
'gitlab.autoGenerate',
|
||||
'gitlab.preRelease',
|
||||
'gitlab.draft',
|
||||
'gitlab.useIdsForUrls',
|
||||
'gitlab.useGenericPackageRepositoryForAssets',
|
||||
'gitlab.skipChecks',
|
||||
'npm.publish',
|
||||
'npm.ignoreVersion',
|
||||
'npm.allowSameVersion',
|
||||
'npm.skipChecks'
|
||||
];
|
||||
|
||||
export const parseCliArguments = args => {
|
||||
const options = parseArgs(args, {
|
||||
boolean: booleanOptions,
|
||||
alias: aliases,
|
||||
configuration: {
|
||||
'parse-numbers': false,
|
||||
'camel-case-expansion': false
|
||||
}
|
||||
});
|
||||
if (options.V) {
|
||||
options.verbose = typeof options.V === 'boolean' ? options.V : options.V.length;
|
||||
delete options.V;
|
||||
}
|
||||
options.increment = options._[0] || options.i;
|
||||
return options;
|
||||
};
|
||||
41
node_modules/release-it/lib/cli.js
generated
vendored
Normal file
41
node_modules/release-it/lib/cli.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import { readJSON } from './util.js';
|
||||
import runTasks from './index.js';
|
||||
|
||||
const pkg = readJSON(new URL('../package.json', import.meta.url));
|
||||
|
||||
const helpText = `Release It! v${pkg.version}
|
||||
|
||||
Usage: release-it <increment> [options]
|
||||
|
||||
Use e.g. "release-it minor" directly as shorthand for "release-it --increment=minor".
|
||||
|
||||
-c --config Path to local configuration options [default: ".release-it.json"]
|
||||
-d --dry-run Do not touch or write anything, but show the commands
|
||||
-h --help Print this help
|
||||
-i --increment Increment "major", "minor", "patch", or "pre*" version; or specify version [default: "patch"]
|
||||
--ci No prompts, no user interaction; activated automatically in CI environments
|
||||
--only-version Prompt only for version, no further interaction
|
||||
-v --version Print release-it version number
|
||||
--release-version Print version number to be released
|
||||
--changelog Print changelog for the version to be released
|
||||
-V --verbose Verbose output (user hooks output)
|
||||
-VV Extra verbose output (also internal commands output)
|
||||
|
||||
For more details, please see https://github.com/release-it/release-it`;
|
||||
|
||||
/** @internal */
|
||||
export const version = () => console.log(`v${pkg.version}`);
|
||||
|
||||
/** @internal */
|
||||
export const help = () => console.log(helpText);
|
||||
|
||||
export default async options => {
|
||||
if (options.version) {
|
||||
version();
|
||||
} else if (options.help) {
|
||||
help();
|
||||
} else {
|
||||
return runTasks(options);
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
181
node_modules/release-it/lib/config.js
generated
vendored
Normal file
181
node_modules/release-it/lib/config.js
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
import util from 'node:util';
|
||||
import assert from 'node:assert';
|
||||
import { isCI } from 'ci-info';
|
||||
import defaultsDeep from '@nodeutils/defaults-deep';
|
||||
import { isObjectStrict } from '@phun-ky/typeof';
|
||||
import merge from 'lodash.merge';
|
||||
import { loadConfig as loadC12 } from 'c12';
|
||||
import { get, getSystemInfo, readJSON } from './util.js';
|
||||
|
||||
const debug = util.debug('release-it:config');
|
||||
const defaultConfig = readJSON(new URL('../config/release-it.json', import.meta.url));
|
||||
|
||||
class Config {
|
||||
constructor(config = {}) {
|
||||
this.constructorConfig = config;
|
||||
this.contextOptions = {};
|
||||
debug({ system: getSystemInfo() });
|
||||
}
|
||||
|
||||
async init() {
|
||||
await loadOptions(this.constructorConfig).then(({ options, localConfig }) => {
|
||||
this._options = options;
|
||||
this._localConfig = localConfig;
|
||||
debug(this._options);
|
||||
});
|
||||
}
|
||||
|
||||
getContext(path) {
|
||||
const context = merge({}, this.options, this.contextOptions);
|
||||
return path ? get(context, path) : context;
|
||||
}
|
||||
|
||||
setContext(options) {
|
||||
debug(options);
|
||||
merge(this.contextOptions, options);
|
||||
}
|
||||
|
||||
setCI(value = true) {
|
||||
this.options.ci = value;
|
||||
}
|
||||
|
||||
get defaultConfig() {
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
get isDryRun() {
|
||||
return Boolean(this.options['dry-run']);
|
||||
}
|
||||
|
||||
get isIncrement() {
|
||||
return this.options.increment !== false;
|
||||
}
|
||||
|
||||
get isVerbose() {
|
||||
return Boolean(this.options.verbose);
|
||||
}
|
||||
|
||||
get verbosityLevel() {
|
||||
return this.options.verbose;
|
||||
}
|
||||
|
||||
get isDebug() {
|
||||
return debug.enabled;
|
||||
}
|
||||
|
||||
get isCI() {
|
||||
return Boolean(this.options.ci) || this.isReleaseVersion || this.isChangelog;
|
||||
}
|
||||
|
||||
get isPromptOnlyVersion() {
|
||||
return this.options['only-version'];
|
||||
}
|
||||
|
||||
get isReleaseVersion() {
|
||||
return Boolean(this.options['release-version']);
|
||||
}
|
||||
|
||||
get isChangelog() {
|
||||
return Boolean(this.options['changelog']);
|
||||
}
|
||||
|
||||
get options() {
|
||||
assert(this._options, `The "options" not resolve yet`);
|
||||
return this._options;
|
||||
}
|
||||
|
||||
get localConfig() {
|
||||
assert(this._localConfig, `The "localConfig" not resolve yet`);
|
||||
return this._localConfig;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOptions(constructorConfig) {
|
||||
const localConfig = await loadLocalConfig(constructorConfig);
|
||||
const merged = defaultsDeep(
|
||||
{},
|
||||
constructorConfig,
|
||||
{
|
||||
ci: isCI
|
||||
},
|
||||
localConfig,
|
||||
defaultConfig
|
||||
);
|
||||
const expanded = expandPreReleaseShorthand(merged);
|
||||
|
||||
return {
|
||||
options: expanded,
|
||||
localConfig
|
||||
};
|
||||
}
|
||||
|
||||
function expandPreReleaseShorthand(options) {
|
||||
const { increment, preRelease, preReleaseId, snapshot, preReleaseBase } = options;
|
||||
const isPreRelease = Boolean(preRelease) || Boolean(snapshot);
|
||||
const inc = snapshot ? 'prerelease' : increment;
|
||||
const preId = typeof preRelease === 'string' ? preRelease : typeof snapshot === 'string' ? snapshot : preReleaseId;
|
||||
options.version = {
|
||||
increment: inc,
|
||||
isPreRelease,
|
||||
preReleaseId: preId,
|
||||
preReleaseBase
|
||||
};
|
||||
if (typeof snapshot === 'string' && options.git) {
|
||||
// Pre set and hard code some options
|
||||
options.git.tagMatch = `0.0.0-${snapshot}.[0-9]*`;
|
||||
options.git.getLatestTagFromAllRefs = true;
|
||||
options.git.requireBranch = '!main';
|
||||
options.git.requireUpstream = false;
|
||||
options.npm.ignoreVersion = true;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
async function loadLocalConfig(constructorConfig) {
|
||||
const file = resolveFile();
|
||||
const dir = resolveDir();
|
||||
const extend = resolveExtend();
|
||||
const defaultConfig = resolveDefaultConfig();
|
||||
|
||||
if (file === false) return {};
|
||||
|
||||
const resolvedConfig = await loadC12({
|
||||
name: 'release-it',
|
||||
configFile: file,
|
||||
packageJson: true,
|
||||
rcFile: false,
|
||||
envName: false,
|
||||
cwd: dir,
|
||||
extend,
|
||||
defaultConfig
|
||||
}).catch(() => {
|
||||
throw new Error(`Invalid configuration file at ${file}`);
|
||||
});
|
||||
|
||||
debug('Loaded local config', resolvedConfig.config);
|
||||
return isObjectStrict(resolvedConfig.config) ? resolvedConfig.config : {};
|
||||
|
||||
function resolveFile() {
|
||||
if (constructorConfig.config === false) return false;
|
||||
|
||||
if (constructorConfig.config === true) return '.release-it';
|
||||
|
||||
return constructorConfig.config ?? '.release-it';
|
||||
}
|
||||
|
||||
function resolveDir() {
|
||||
return constructorConfig.configDir ?? process.cwd();
|
||||
}
|
||||
|
||||
function resolveExtend() {
|
||||
return constructorConfig.extends === false ? false : undefined;
|
||||
}
|
||||
|
||||
function resolveDefaultConfig() {
|
||||
return {
|
||||
extends: constructorConfig.extends === false ? undefined : constructorConfig.extends
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Config;
|
||||
163
node_modules/release-it/lib/index.js
generated
vendored
Normal file
163
node_modules/release-it/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
import { getPlugins } from './plugin/factory.js';
|
||||
import Logger from './log.js';
|
||||
import Config from './config.js';
|
||||
import Shell from './shell.js';
|
||||
import Prompt from './prompt.js';
|
||||
import Spinner from './spinner.js';
|
||||
import { reduceUntil, parseVersion, castArray } from './util.js';
|
||||
|
||||
const runTasks = async (opts, di) => {
|
||||
let container = {};
|
||||
|
||||
try {
|
||||
Object.assign(container, di);
|
||||
container.config = container.config || new Config(opts);
|
||||
await container.config.init();
|
||||
|
||||
const { config } = container;
|
||||
const { isCI, isVerbose, verbosityLevel, isDryRun, isChangelog, isReleaseVersion } = config;
|
||||
|
||||
container.log = container.log || new Logger({ isCI, isVerbose, verbosityLevel, isDryRun });
|
||||
container.spinner = container.spinner || new Spinner({ container, config });
|
||||
container.prompt = container.prompt || new Prompt({ container: { config } });
|
||||
container.shell = container.shell || new Shell({ container });
|
||||
|
||||
const { log, shell, spinner } = container;
|
||||
|
||||
const options = config.getContext();
|
||||
|
||||
const { hooks } = options;
|
||||
|
||||
const runHook = async (...name) => {
|
||||
const scripts = hooks[name.join(':')];
|
||||
if (!scripts || !scripts.length) return;
|
||||
const context = config.getContext();
|
||||
const external = true;
|
||||
for (const script of castArray(scripts)) {
|
||||
const task = () => shell.exec(script, { external }, context);
|
||||
await spinner.show({ task, label: script, context, external });
|
||||
}
|
||||
};
|
||||
|
||||
const runLifeCycleHook = async (plugin, name, ...args) => {
|
||||
if (plugin === plugins.at(0)) await runHook('before', name);
|
||||
await runHook('before', plugin.namespace, name);
|
||||
const willHookRun = (await plugin[name](...args)) !== false;
|
||||
if (willHookRun) {
|
||||
await runHook('after', plugin.namespace, name);
|
||||
}
|
||||
if (plugin === plugins.at(-1)) await runHook('after', name);
|
||||
};
|
||||
|
||||
const [internal, external] = await getPlugins(config, container);
|
||||
let plugins = [...external, ...internal];
|
||||
|
||||
for (const plugin of plugins) {
|
||||
await runLifeCycleHook(plugin, 'init');
|
||||
}
|
||||
|
||||
const { increment, isPreRelease, preReleaseId, preReleaseBase } = options.version;
|
||||
|
||||
const name = await reduceUntil(plugins, plugin => plugin.getName());
|
||||
const latestVersion = (await reduceUntil(plugins, plugin => plugin.getLatestVersion())) || '0.0.0';
|
||||
const changelog = await reduceUntil(plugins, plugin => plugin.getChangelog(latestVersion));
|
||||
|
||||
if (isChangelog) {
|
||||
if (changelog) {
|
||||
console.log(changelog);
|
||||
process.exit(0);
|
||||
} else {
|
||||
log.warn('No changelog found');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const incrementBase = { latestVersion, increment, isPreRelease, preReleaseId, preReleaseBase };
|
||||
|
||||
const { snapshot } = config.options;
|
||||
if (snapshot && (!incrementBase.latestVersion.startsWith('0.0.0') || incrementBase.latestVersion === '0.0.0')) {
|
||||
// Reading the latest version first allows to increment the final counter, fake it if it's not a snapshot:
|
||||
incrementBase.latestVersion = `0.0.0-0`;
|
||||
}
|
||||
|
||||
let version;
|
||||
if (config.isIncrement) {
|
||||
incrementBase.increment = await reduceUntil(plugins, plugin => plugin.getIncrement(incrementBase));
|
||||
version = await reduceUntil(plugins, plugin => plugin.getIncrementedVersionCI(incrementBase));
|
||||
} else {
|
||||
version = latestVersion;
|
||||
}
|
||||
|
||||
config.setContext({ name, latestVersion, version, changelog });
|
||||
|
||||
if (!isReleaseVersion) {
|
||||
const action = config.isIncrement ? 'release' : 'update';
|
||||
const suffix = version && config.isIncrement ? `${latestVersion}...${version}` : `currently at ${latestVersion}`;
|
||||
log.obtrusive(`🚀 Let's ${action} ${name} (${suffix})`);
|
||||
log.preview({ title: 'changelog', text: changelog });
|
||||
}
|
||||
|
||||
if (config.isIncrement) {
|
||||
version = version || (await reduceUntil(plugins, plugin => plugin.getIncrementedVersion(incrementBase)));
|
||||
}
|
||||
|
||||
if (isReleaseVersion) {
|
||||
if (version) {
|
||||
console.log(version);
|
||||
process.exit(0);
|
||||
} else {
|
||||
log.warn(`No new version to release`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (version) {
|
||||
config.setContext(parseVersion(version));
|
||||
|
||||
if (config.isPromptOnlyVersion) {
|
||||
config.setCI(true);
|
||||
}
|
||||
|
||||
for (const hook of ['beforeBump', 'bump', 'beforeRelease']) {
|
||||
for (const plugin of plugins) {
|
||||
const args = hook === 'bump' ? [version] : [];
|
||||
await runLifeCycleHook(plugin, hook, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
plugins = [...internal, ...external];
|
||||
|
||||
for (const hook of ['release', 'afterRelease']) {
|
||||
for (const plugin of plugins) {
|
||||
await runLifeCycleHook(plugin, hook);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.obtrusive(`No new version to release`);
|
||||
}
|
||||
|
||||
log.log(`🏁 Done (in ${Math.floor(process.uptime())}s.)`);
|
||||
|
||||
return {
|
||||
name,
|
||||
changelog,
|
||||
latestVersion,
|
||||
version
|
||||
};
|
||||
} catch (err) {
|
||||
const { log } = container;
|
||||
|
||||
const errorMessage = err.message || err;
|
||||
const logger = log || console;
|
||||
|
||||
err.cause === 'INFO' ? logger.info(errorMessage) : logger.error(errorMessage);
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export default runTasks;
|
||||
|
||||
export { default as Config } from './config.js';
|
||||
|
||||
export { default as Plugin } from './plugin/Plugin.js';
|
||||
70
node_modules/release-it/lib/log.js
generated
vendored
Normal file
70
node_modules/release-it/lib/log.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import { EOL } from 'node:os';
|
||||
import { styleText } from 'node:util';
|
||||
import { isObjectLoose } from '@phun-ky/typeof';
|
||||
import { upperFirst } from './util.js';
|
||||
|
||||
class Logger {
|
||||
constructor({ isCI = true, isVerbose = false, verbosityLevel = 0, isDryRun = false } = {}) {
|
||||
this.isCI = isCI;
|
||||
this.isVerbose = isVerbose;
|
||||
this.verbosityLevel = verbosityLevel;
|
||||
this.isDryRun = isDryRun;
|
||||
}
|
||||
|
||||
shouldLog(isExternal) {
|
||||
return this.verbosityLevel === 2 || (this.isVerbose && isExternal);
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
console.log(...args);
|
||||
}
|
||||
|
||||
error(...args) {
|
||||
console.error([styleText('red', 'ERROR'), ...args].join(' '));
|
||||
}
|
||||
|
||||
info(...args) {
|
||||
console.error(styleText('grey', args.join(' ')));
|
||||
}
|
||||
|
||||
warn(...args) {
|
||||
console.error([styleText('yellow', 'WARNING'), ...args].join(' '));
|
||||
}
|
||||
|
||||
verbose(...args) {
|
||||
const { isExternal } = isObjectLoose(args.at(-1)) ? args.at(-1) : {};
|
||||
if (this.shouldLog(isExternal)) {
|
||||
console.error(...args.filter(str => typeof str === 'string'));
|
||||
}
|
||||
}
|
||||
|
||||
exec(...args) {
|
||||
const { isDryRun: isExecutedInDryRun, isExternal, isCached } = isObjectLoose(args.at(-1)) ? args.at(-1) : {};
|
||||
if (this.shouldLog(isExternal) || this.isDryRun) {
|
||||
const prefix = isExecutedInDryRun == null ? '$' : '!';
|
||||
const command = args
|
||||
.map(cmd => (typeof cmd === 'string' ? cmd : Array.isArray(cmd) ? cmd.join(' ') : ''))
|
||||
.join(' ');
|
||||
const message = [prefix, command, isCached ? '[cached]' : ''].join(' ').trim();
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
obtrusive(...args) {
|
||||
if (!this.isCI) this.log();
|
||||
this.log(...args);
|
||||
if (!this.isCI) this.log();
|
||||
}
|
||||
|
||||
preview({ title, text }) {
|
||||
if (text) {
|
||||
const header = styleText('bold', upperFirst(title));
|
||||
const body = text.replace(new RegExp(EOL + EOL, 'g'), EOL);
|
||||
this.obtrusive(`${header}:${EOL}${body}`);
|
||||
} else {
|
||||
this.obtrusive(`Empty ${title.toLowerCase()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Logger;
|
||||
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;
|
||||
33
node_modules/release-it/lib/prompt.js
generated
vendored
Normal file
33
node_modules/release-it/lib/prompt.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
class Prompt {
|
||||
constructor({ container }) {
|
||||
this.createPrompt = (container.inquirer || inquirer).prompt;
|
||||
this.prompts = {};
|
||||
}
|
||||
|
||||
register(pluginPrompts, namespace = 'default') {
|
||||
this.prompts[namespace] = this.prompts[namespace] || {};
|
||||
Object.assign(this.prompts[namespace], pluginPrompts);
|
||||
}
|
||||
|
||||
async show({ enabled = true, prompt: promptName, namespace = 'default', task, context }) {
|
||||
if (!enabled) return false;
|
||||
|
||||
const prompt = this.prompts[namespace][promptName];
|
||||
const options = Object.assign({}, prompt, {
|
||||
name: promptName,
|
||||
message: prompt.message(context),
|
||||
choices: 'choices' in prompt && prompt.choices(context),
|
||||
transformer: 'transformer' in prompt ? prompt.transformer(context) : undefined
|
||||
});
|
||||
|
||||
const answers = await this.createPrompt([options]);
|
||||
|
||||
const doExecute = prompt.type === 'confirm' ? answers[promptName] : true;
|
||||
|
||||
return doExecute && task ? await task(answers[promptName]) : false;
|
||||
}
|
||||
}
|
||||
|
||||
export default Prompt;
|
||||
120
node_modules/release-it/lib/shell.js
generated
vendored
Normal file
120
node_modules/release-it/lib/shell.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
import util from 'node:util';
|
||||
import { spawn, exec } from 'node:child_process';
|
||||
import { format } from './util.js';
|
||||
|
||||
const debug = util.debug('release-it:shell');
|
||||
|
||||
const noop = Promise.resolve();
|
||||
|
||||
class Shell {
|
||||
constructor({ container }) {
|
||||
this.log = container.log;
|
||||
this.config = container.config;
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
exec(command, options = {}, context = {}) {
|
||||
if (!command || !command.length) return;
|
||||
return typeof command === 'string'
|
||||
? this.execFormattedCommand(format(command, context), options)
|
||||
: this.execFormattedCommand(command, options);
|
||||
}
|
||||
|
||||
async execFormattedCommand(command, options = {}) {
|
||||
const { isDryRun } = this.config;
|
||||
const isWrite = options.write !== false;
|
||||
const isExternal = options.external === true;
|
||||
const cacheKey = typeof command === 'string' ? command : command.join(' ');
|
||||
const isCached = !isExternal && this.cache.has(cacheKey);
|
||||
|
||||
if (isDryRun && isWrite) {
|
||||
this.log.exec(command, { isDryRun });
|
||||
return noop;
|
||||
}
|
||||
|
||||
this.log.exec(command, { isExternal, isCached });
|
||||
|
||||
if (isCached) {
|
||||
return this.cache.get(cacheKey);
|
||||
}
|
||||
|
||||
const result =
|
||||
typeof command === 'string'
|
||||
? this.execStringCommand(command, options, { isExternal })
|
||||
: this.execWithArguments(command, options, { isExternal });
|
||||
|
||||
if (!isExternal && !this.cache.has(cacheKey)) {
|
||||
this.cache.set(cacheKey, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
execStringCommand(command, options, { isExternal }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = exec(command, (err, stdout, stderr) => {
|
||||
stdout = stdout.toString().trimEnd();
|
||||
const code = !err ? 0 : err === 'undefined' ? 1 : err.code;
|
||||
debug({ command, options, code, stdout, stderr });
|
||||
if (code === 0) {
|
||||
resolve(stdout);
|
||||
} else {
|
||||
reject(new Error(stderr || stdout));
|
||||
}
|
||||
});
|
||||
proc.stdout.on('data', stdout => this.log.verbose(stdout.toString().trimEnd(), { isExternal }));
|
||||
proc.stderr.on('data', stderr => this.log.verbose(stderr.toString().trimEnd(), { isExternal }));
|
||||
});
|
||||
}
|
||||
|
||||
async execWithArguments(command, options = {}, { isExternal } = {}) {
|
||||
const [program, ...programArgs] = command;
|
||||
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const proc = spawn(program, programArgs, {
|
||||
// we want to capture all output from the process so the extra 2 pipe
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
...options
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
proc.stdout.on('data', data => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
proc.stderr.on('data', data => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
proc.on('close', code => {
|
||||
stdout = stdout === '""' ? '' : stdout;
|
||||
this.log.verbose(stdout, { isExternal });
|
||||
debug({ command, options, stdout, stderr });
|
||||
|
||||
if (code === 0) {
|
||||
resolve((stdout || stderr).trim());
|
||||
} else {
|
||||
if (stdout) {
|
||||
this.log.log(`\n${stdout}`);
|
||||
}
|
||||
debug({ code, command, options, stdout, stderr });
|
||||
reject(new Error(stderr || stdout || `Process exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('error', err => {
|
||||
debug(err);
|
||||
reject(new Error(err.message));
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Shell;
|
||||
29
node_modules/release-it/lib/spinner.js
generated
vendored
Normal file
29
node_modules/release-it/lib/spinner.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import { oraPromise } from 'ora';
|
||||
import { format } from './util.js';
|
||||
|
||||
const noop = Promise.resolve();
|
||||
|
||||
class Spinner {
|
||||
constructor({ container = {} } = {}) {
|
||||
this.config = container.config;
|
||||
this.ora = container.ora || oraPromise;
|
||||
}
|
||||
show({ enabled = true, task, label, external = false, context }) {
|
||||
if (!enabled) return noop;
|
||||
|
||||
const { config } = this;
|
||||
this.isSpinnerDisabled = !config.isCI || config.isVerbose || config.isDryRun || config.isDebug;
|
||||
this.canForce = !config.isCI && !config.isVerbose && !config.isDryRun && !config.isDebug;
|
||||
|
||||
const awaitTask = task();
|
||||
|
||||
if (!this.isSpinnerDisabled || (external && this.canForce)) {
|
||||
const text = format(label, context);
|
||||
this.ora(awaitTask, text);
|
||||
}
|
||||
|
||||
return awaitTask;
|
||||
}
|
||||
}
|
||||
|
||||
export default Spinner;
|
||||
206
node_modules/release-it/lib/util.js
generated
vendored
Normal file
206
node_modules/release-it/lib/util.js
generated
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
import fs, { close, openSync, statSync, utimesSync, accessSync } from 'node:fs'; // need import fs here due to test stubbing
|
||||
import util from 'node:util';
|
||||
import { EOL } from 'node:os';
|
||||
import gitUrlParse from 'git-url-parse';
|
||||
import semver from 'semver';
|
||||
import osName from 'os-name';
|
||||
import { Eta } from 'eta';
|
||||
import Log from './log.js';
|
||||
|
||||
const debug = util.debug('release-it:shell');
|
||||
|
||||
const eta = new Eta({
|
||||
autoEscape: false,
|
||||
useWith: true,
|
||||
tags: ['${', '}'],
|
||||
parse: { interpolate: '' },
|
||||
rmWhitespace: false,
|
||||
autoTrim: false
|
||||
});
|
||||
|
||||
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const before = (n, func) => {
|
||||
var result;
|
||||
if (typeof func != 'function') {
|
||||
throw new TypeError('Missing argument for `func`');
|
||||
}
|
||||
n = parseInt(n);
|
||||
return function () {
|
||||
if (--n > 0) {
|
||||
result = func.apply(this, arguments);
|
||||
}
|
||||
if (n <= 1) {
|
||||
func = undefined;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
const tryStatFile = filePath => {
|
||||
try {
|
||||
return statSync(filePath);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export const execOpts = {
|
||||
stdio: process.env.NODE_DEBUG && process.env.NODE_DEBUG.indexOf('release-it') === 0 ? 'pipe' : []
|
||||
};
|
||||
|
||||
export const readJSON = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
|
||||
const pkg = readJSON(new URL('../package.json', import.meta.url));
|
||||
|
||||
export const getSystemInfo = () => {
|
||||
return {
|
||||
'release-it': pkg.version,
|
||||
node: process.version,
|
||||
os: osName()
|
||||
};
|
||||
};
|
||||
|
||||
export const format = (template = '', context = {}) => {
|
||||
if (!context || context === null || !template || template === null || template.indexOf('${') === -1) return template;
|
||||
const log = new Log();
|
||||
try {
|
||||
return eta.renderString(template, context);
|
||||
} catch (error) {
|
||||
log.error(`Unable to render template with context:\n${template}\n${JSON.stringify(context)}`);
|
||||
log.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const truncateLines = (input, maxLines = 10, surplusText = null) => {
|
||||
const lines = input.split(EOL);
|
||||
const surplus = lines.length - maxLines;
|
||||
const output = lines.slice(0, maxLines).join(EOL);
|
||||
return surplus > 0 ? (surplusText ? `${output}${surplusText}` : `${output}${EOL}...and ${surplus} more`) : output;
|
||||
};
|
||||
|
||||
export const rejectAfter = (ms, error) =>
|
||||
wait(ms).then(() => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
export const parseGitUrl = remoteUrl => {
|
||||
if (!remoteUrl) return { host: null, owner: null, project: null, protocol: null, remote: null, repository: null };
|
||||
const normalizedUrl = (remoteUrl || '')
|
||||
.replace(/^[A-Z]:\\\\/, 'file://') // Assume file protocol for Windows drive letters
|
||||
.replace(/^\//, 'file://') // Assume file protocol if only /path is given
|
||||
.replace(/\\+/g, '/'); // Replace forward with backslashes
|
||||
const parsedUrl = gitUrlParse(normalizedUrl);
|
||||
const { resource: host, name: project, protocol, href: remote } = parsedUrl;
|
||||
const owner = protocol === 'file' ? parsedUrl.owner.split('/').at(-1) : parsedUrl.owner; // Fix owner for file protocol
|
||||
const repository = `${owner}/${project}`;
|
||||
return { host, owner, project, protocol, remote, repository };
|
||||
};
|
||||
|
||||
export const reduceUntil = async (collection, fn) => {
|
||||
let result;
|
||||
for (const item of collection) {
|
||||
if (result) break;
|
||||
result = await fn(item);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const hasAccess = path => {
|
||||
try {
|
||||
accessSync(path);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const parseVersion = raw => {
|
||||
if (raw == null) return { version: raw, isPreRelease: false, preReleaseId: null };
|
||||
const version = semver.valid(raw) ? raw : semver.coerce(raw);
|
||||
if (!version) return { version: raw, isPreRelease: false, preReleaseId: null };
|
||||
const parsed = semver.parse(version);
|
||||
const isPreRelease = parsed.prerelease.length > 0;
|
||||
const preReleaseId = isPreRelease && isNaN(parsed.prerelease[0]) ? parsed.prerelease[0] : null;
|
||||
return {
|
||||
version: version.toString(),
|
||||
isPreRelease,
|
||||
preReleaseId
|
||||
};
|
||||
};
|
||||
|
||||
export const e = (message, docs, fail = true) => {
|
||||
const error = new Error(docs ? `${message}${EOL}Documentation: ${docs}${EOL}` : message);
|
||||
error.code = fail ? 1 : 0;
|
||||
error.cause = fail ? 'ERROR' : 'INFO';
|
||||
return error;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export const touch = (path, callback) => {
|
||||
const stat = tryStatFile(path);
|
||||
if (stat && stat.isDirectory()) {
|
||||
// don't error just exit
|
||||
return;
|
||||
}
|
||||
|
||||
const fd = openSync(path, 'a');
|
||||
close(fd);
|
||||
const now = new Date();
|
||||
const mtime = now;
|
||||
const atime = now;
|
||||
utimesSync(path, atime, mtime);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const fixArgs = args => (args ? (typeof args === 'string' ? args.split(' ') : args) : []);
|
||||
|
||||
export const upperFirst = string => {
|
||||
return string ? string.charAt(0).toUpperCase() + string.slice(1) : '';
|
||||
};
|
||||
|
||||
export const castArray = arr => {
|
||||
return Array.isArray(arr) ? arr : [arr];
|
||||
};
|
||||
|
||||
export const once = fn => {
|
||||
return before(2, fn);
|
||||
};
|
||||
|
||||
export const pick = (object, keys) => {
|
||||
return keys.reduce((obj, key) => {
|
||||
if (object && Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
obj[key] = object[key];
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const parsePath = path => {
|
||||
const result = [];
|
||||
const regex = /[^.[\]]+|\[(?:(\d+)|["'](.+?)["'])\]/g;
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(path)) !== null) {
|
||||
result.push(match[1] ?? match[2] ?? match[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const get = (obj, path, defaultValue = undefined) => {
|
||||
if (!path || typeof path !== 'string') {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try {
|
||||
const keys = parsePath(path);
|
||||
return keys.reduce((acc, key) => acc?.[key], obj) ?? defaultValue;
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user