release-it 0.0.0-pl.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +421 -0
- package/bin/release-it.js +42 -0
- package/config/release-it.json +70 -0
- package/lib/cli.js +44 -0
- package/lib/config.js +139 -0
- package/lib/index.js +152 -0
- package/lib/log.js +69 -0
- package/lib/plugin/GitBase.js +125 -0
- package/lib/plugin/GitRelease.js +58 -0
- package/lib/plugin/Plugin.js +73 -0
- package/lib/plugin/factory.js +89 -0
- package/lib/plugin/git/Git.js +220 -0
- package/lib/plugin/git/prompts.js +19 -0
- package/lib/plugin/github/GitHub.js +403 -0
- package/lib/plugin/github/prompts.js +16 -0
- package/lib/plugin/github/util.js +39 -0
- package/lib/plugin/gitlab/GitLab.js +277 -0
- package/lib/plugin/gitlab/prompts.js +9 -0
- package/lib/plugin/npm/npm.js +281 -0
- package/lib/plugin/npm/prompts.js +12 -0
- package/lib/plugin/version/Version.js +129 -0
- package/lib/prompt.js +33 -0
- package/lib/shell.js +91 -0
- package/lib/spinner.js +29 -0
- package/lib/util.js +109 -0
- package/package.json +122 -0
- package/test/cli.js +20 -0
- package/test/config.js +144 -0
- package/test/git.init.js +250 -0
- package/test/git.js +358 -0
- package/test/github.js +487 -0
- package/test/gitlab.js +252 -0
- package/test/log.js +143 -0
- package/test/npm.js +417 -0
- package/test/plugin-name.js +9 -0
- package/test/plugins.js +238 -0
- package/test/prompt.js +97 -0
- package/test/resources/file-v2.0.1.txt +1 -0
- package/test/resources/file-v2.0.2.txt +1 -0
- package/test/resources/file1 +1 -0
- package/test/shell.js +74 -0
- package/test/spinner.js +58 -0
- package/test/stub/config/default/.release-it.json +5 -0
- package/test/stub/config/invalid-config-rc +1 -0
- package/test/stub/config/invalid-config-txt +2 -0
- package/test/stub/config/merge/.release-it.json +5 -0
- package/test/stub/config/merge/package.json +7 -0
- package/test/stub/config/toml/.release-it.toml +2 -0
- package/test/stub/config/yaml/.release-it.yaml +2 -0
- package/test/stub/config/yml/.release-it.yml +2 -0
- package/test/stub/github.js +130 -0
- package/test/stub/gitlab.js +44 -0
- package/test/stub/plugin-context.js +36 -0
- package/test/stub/plugin-replace.js +9 -0
- package/test/stub/plugin.js +39 -0
- package/test/stub/shell.js +24 -0
- package/test/tasks.interactive.js +208 -0
- package/test/tasks.js +585 -0
- package/test/util/helpers.js +32 -0
- package/test/util/index.js +78 -0
- package/test/util/setup.js +5 -0
- package/test/utils.js +97 -0
- package/test/version.js +173 -0
package/lib/config.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import util from 'node:util';
|
|
2
|
+
import { cosmiconfigSync } from 'cosmiconfig';
|
|
3
|
+
import parseToml from '@iarna/toml/parse-string.js';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
import isCI from 'is-ci';
|
|
6
|
+
import { readJSON, getSystemInfo } from './util.js';
|
|
7
|
+
|
|
8
|
+
const debug = util.debug('release-it:config');
|
|
9
|
+
const defaultConfig = readJSON(new URL('../config/release-it.json', import.meta.url));
|
|
10
|
+
|
|
11
|
+
const searchPlaces = [
|
|
12
|
+
'package.json',
|
|
13
|
+
'.release-it.json',
|
|
14
|
+
'.release-it.js',
|
|
15
|
+
'.release-it.cjs',
|
|
16
|
+
'.release-it.yaml',
|
|
17
|
+
'.release-it.yml',
|
|
18
|
+
'.release-it.toml'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const loaders = {
|
|
22
|
+
'.toml': (_, content) => parseToml(content)
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getLocalConfig = ({ file, dir = process.cwd() }) => {
|
|
26
|
+
let localConfig = {};
|
|
27
|
+
if (file === false) return localConfig;
|
|
28
|
+
const explorer = cosmiconfigSync('release-it', {
|
|
29
|
+
searchPlaces,
|
|
30
|
+
loaders
|
|
31
|
+
});
|
|
32
|
+
const result = file ? explorer.load(file) : explorer.search(dir);
|
|
33
|
+
if (result && typeof result.config === 'string') {
|
|
34
|
+
throw new Error(`Invalid configuration file at ${result.filepath}`);
|
|
35
|
+
}
|
|
36
|
+
debug({ cosmiconfig: result });
|
|
37
|
+
return result && _.isPlainObject(result.config) ? result.config : localConfig;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
class Config {
|
|
41
|
+
constructor(config = {}) {
|
|
42
|
+
this.constructorConfig = config;
|
|
43
|
+
this.localConfig = getLocalConfig({ file: config.config, dir: config.configDir });
|
|
44
|
+
this.options = this.mergeOptions();
|
|
45
|
+
this.options = this.expandPreReleaseShorthand(this.options);
|
|
46
|
+
this.contextOptions = {};
|
|
47
|
+
debug({ system: getSystemInfo() });
|
|
48
|
+
debug(this.options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
expandPreReleaseShorthand(options) {
|
|
52
|
+
const { increment, preRelease, preReleaseId, snapshot } = options;
|
|
53
|
+
const isPreRelease = Boolean(preRelease) || Boolean(snapshot);
|
|
54
|
+
const inc = snapshot ? 'prerelease' : increment;
|
|
55
|
+
const preId = typeof preRelease === 'string' ? preRelease : typeof snapshot === 'string' ? snapshot : preReleaseId;
|
|
56
|
+
options.version = {
|
|
57
|
+
increment: inc,
|
|
58
|
+
isPreRelease,
|
|
59
|
+
preReleaseId: preId
|
|
60
|
+
};
|
|
61
|
+
if (typeof snapshot === 'string' && options.git) {
|
|
62
|
+
// Pre set and hard code some options
|
|
63
|
+
options.git.tagMatch = `0.0.0-${snapshot}.[0-9]*`;
|
|
64
|
+
options.git.getLatestTagFromAllRefs = true;
|
|
65
|
+
options.git.requireBranch = '!main';
|
|
66
|
+
options.git.requireUpstream = false;
|
|
67
|
+
options.npm.ignoreVersion = true;
|
|
68
|
+
}
|
|
69
|
+
return options;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
mergeOptions() {
|
|
73
|
+
return _.defaultsDeep(
|
|
74
|
+
{},
|
|
75
|
+
this.constructorConfig,
|
|
76
|
+
{
|
|
77
|
+
ci: isCI
|
|
78
|
+
},
|
|
79
|
+
this.localConfig,
|
|
80
|
+
this.defaultConfig
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getContext(path) {
|
|
85
|
+
const context = _.merge({}, this.options, this.contextOptions);
|
|
86
|
+
return path ? _.get(context, path) : context;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setContext(options) {
|
|
90
|
+
debug(options);
|
|
91
|
+
_.merge(this.contextOptions, options);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setCI(value = true) {
|
|
95
|
+
this.options.ci = value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get defaultConfig() {
|
|
99
|
+
return defaultConfig;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get isDryRun() {
|
|
103
|
+
return Boolean(this.options['dry-run']);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get isIncrement() {
|
|
107
|
+
return this.options.increment !== false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get isVerbose() {
|
|
111
|
+
return Boolean(this.options.verbose);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get verbosityLevel() {
|
|
115
|
+
return this.options.verbose;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get isDebug() {
|
|
119
|
+
return debug.enabled;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get isCI() {
|
|
123
|
+
return Boolean(this.options.ci) || this.isReleaseVersion || this.isChangelog;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get isPromptOnlyVersion() {
|
|
127
|
+
return this.options['only-version'];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get isReleaseVersion() {
|
|
131
|
+
return Boolean(this.options['release-version']);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get isChangelog() {
|
|
135
|
+
return Boolean(this.options['changelog']);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default Config;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { getPlugins } from './plugin/factory.js';
|
|
3
|
+
import Logger from './log.js';
|
|
4
|
+
import Config from './config.js';
|
|
5
|
+
import Shell from './shell.js';
|
|
6
|
+
import Prompt from './prompt.js';
|
|
7
|
+
import Spinner from './spinner.js';
|
|
8
|
+
import { reduceUntil, parseVersion } from './util.js';
|
|
9
|
+
|
|
10
|
+
const runTasks = async (opts, di) => {
|
|
11
|
+
let container = {};
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
Object.assign(container, di);
|
|
15
|
+
container.config = container.config || new Config(opts);
|
|
16
|
+
|
|
17
|
+
const { config } = container;
|
|
18
|
+
const { isCI, isVerbose, verbosityLevel, isDryRun, isChangelog, isReleaseVersion } = config;
|
|
19
|
+
|
|
20
|
+
container.log = container.log || new Logger({ isCI, isVerbose, verbosityLevel, isDryRun });
|
|
21
|
+
container.spinner = container.spinner || new Spinner({ container, config });
|
|
22
|
+
container.prompt = container.prompt || new Prompt({ container: { config } });
|
|
23
|
+
container.shell = container.shell || new Shell({ container });
|
|
24
|
+
|
|
25
|
+
const { log, shell, spinner } = container;
|
|
26
|
+
|
|
27
|
+
const options = config.getContext();
|
|
28
|
+
|
|
29
|
+
const { hooks } = options;
|
|
30
|
+
|
|
31
|
+
const runHook = async (...name) => {
|
|
32
|
+
const scripts = hooks[name.join(':')];
|
|
33
|
+
if (!scripts || !scripts.length) return;
|
|
34
|
+
const context = config.getContext();
|
|
35
|
+
const external = true;
|
|
36
|
+
for (const script of _.castArray(scripts)) {
|
|
37
|
+
const task = () => shell.exec(script, { external }, context);
|
|
38
|
+
await spinner.show({ task, label: script, context, external });
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const runLifeCycleHook = async (plugin, name, ...args) => {
|
|
43
|
+
if (plugin === _.first(plugins)) await runHook('before', name);
|
|
44
|
+
await runHook('before', plugin.namespace, name);
|
|
45
|
+
const willHookRun = (await plugin[name](...args)) !== false;
|
|
46
|
+
if (willHookRun) {
|
|
47
|
+
await runHook('after', plugin.namespace, name);
|
|
48
|
+
}
|
|
49
|
+
if (plugin === _.last(plugins)) await runHook('after', name);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const [internal, external] = await getPlugins(config, container);
|
|
53
|
+
let plugins = [...external, ...internal];
|
|
54
|
+
|
|
55
|
+
for (const plugin of plugins) {
|
|
56
|
+
await runLifeCycleHook(plugin, 'init');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { increment, isPreRelease, preReleaseId } = options.version;
|
|
60
|
+
|
|
61
|
+
const name = await reduceUntil(plugins, plugin => plugin.getName());
|
|
62
|
+
const latestVersion = (await reduceUntil(plugins, plugin => plugin.getLatestVersion())) || '0.0.0';
|
|
63
|
+
const changelog = await reduceUntil(plugins, plugin => plugin.getChangelog(latestVersion));
|
|
64
|
+
|
|
65
|
+
if (isChangelog) {
|
|
66
|
+
console.log(changelog);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const incrementBase = { latestVersion, increment, isPreRelease, preReleaseId };
|
|
71
|
+
|
|
72
|
+
const { snapshot } = config.options;
|
|
73
|
+
if (snapshot && (!incrementBase.latestVersion.startsWith('0.0.0') || incrementBase.latestVersion === '0.0.0')) {
|
|
74
|
+
// Reading the latest version first allows to increment the final counter, fake it if it's not a snapshot:
|
|
75
|
+
incrementBase.latestVersion = `0.0.0-0`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let version;
|
|
79
|
+
if (config.isIncrement) {
|
|
80
|
+
incrementBase.increment = await reduceUntil(plugins, plugin => plugin.getIncrement(incrementBase));
|
|
81
|
+
version = await reduceUntil(plugins, plugin => plugin.getIncrementedVersionCI(incrementBase));
|
|
82
|
+
} else {
|
|
83
|
+
version = latestVersion;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
config.setContext({ name, latestVersion, version, changelog });
|
|
87
|
+
|
|
88
|
+
if (!isReleaseVersion) {
|
|
89
|
+
const action = config.isIncrement ? 'release' : 'update';
|
|
90
|
+
const suffix = version && config.isIncrement ? `${latestVersion}...${version}` : `currently at ${latestVersion}`;
|
|
91
|
+
log.obtrusive(`🚀 Let's ${action} ${name} (${suffix})`);
|
|
92
|
+
log.preview({ title: 'changelog', text: changelog });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (config.isIncrement) {
|
|
96
|
+
version = version || (await reduceUntil(plugins, plugin => plugin.getIncrementedVersion(incrementBase)));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!version) {
|
|
100
|
+
log.obtrusive(`No new version to release`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isReleaseVersion) {
|
|
104
|
+
console.log(version);
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (version) {
|
|
109
|
+
config.setContext(parseVersion(version));
|
|
110
|
+
|
|
111
|
+
if (config.isPromptOnlyVersion) {
|
|
112
|
+
config.setCI(true);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const hook of ['beforeBump', 'bump', 'beforeRelease']) {
|
|
116
|
+
for (const plugin of plugins) {
|
|
117
|
+
const args = hook === 'bump' ? [version] : [];
|
|
118
|
+
await runLifeCycleHook(plugin, hook, ...args);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
plugins = [...internal, ...external];
|
|
123
|
+
|
|
124
|
+
for (const hook of ['release', 'afterRelease']) {
|
|
125
|
+
for (const plugin of plugins) {
|
|
126
|
+
await runLifeCycleHook(plugin, hook);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
log.log(`🏁 Done (in ${Math.floor(process.uptime())}s.)`);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
name,
|
|
135
|
+
changelog,
|
|
136
|
+
latestVersion,
|
|
137
|
+
version
|
|
138
|
+
};
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const { log } = container;
|
|
141
|
+
log ? log.error(err.message || err) : console.error(err); // eslint-disable-line no-console
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export default runTasks;
|
|
147
|
+
|
|
148
|
+
/** @public */
|
|
149
|
+
export { default as Config } from './config.js';
|
|
150
|
+
|
|
151
|
+
/** @public */
|
|
152
|
+
export { default as Plugin } from './plugin/Plugin.js';
|
package/lib/log.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { EOL } from 'node:os';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
|
|
5
|
+
const { isObject, last, filter, isString, lowerCase, upperFirst, isArray } = _;
|
|
6
|
+
|
|
7
|
+
class Logger {
|
|
8
|
+
constructor({ isCI = true, isVerbose = false, verbosityLevel = 0, isDryRun = false } = {}) {
|
|
9
|
+
this.isCI = isCI;
|
|
10
|
+
this.isVerbose = isVerbose;
|
|
11
|
+
this.verbosityLevel = verbosityLevel;
|
|
12
|
+
this.isDryRun = isDryRun;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
shouldLog(isExternal) {
|
|
16
|
+
return this.verbosityLevel === 2 || (this.isVerbose && isExternal);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
log(...args) {
|
|
20
|
+
console.log(...args); // eslint-disable-line no-console
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
error(...args) {
|
|
24
|
+
console.error(chalk.red('ERROR'), ...args); // eslint-disable-line no-console
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
info(...args) {
|
|
28
|
+
this.log(chalk.grey(...args));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
warn(...args) {
|
|
32
|
+
this.log(chalk.yellow('WARNING'), ...args);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
verbose(...args) {
|
|
36
|
+
const { isExternal } = isObject(last(args)) ? last(args) : {};
|
|
37
|
+
if (this.shouldLog(isExternal)) {
|
|
38
|
+
this.log(...filter(args, isString));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exec(...args) {
|
|
43
|
+
const { isDryRun: isExecutedInDryRun, isExternal, isCached } = isObject(last(args)) ? last(args) : {};
|
|
44
|
+
if (this.shouldLog(isExternal) || this.isDryRun) {
|
|
45
|
+
const prefix = isExecutedInDryRun == null ? '$' : '!';
|
|
46
|
+
const command = args.map(cmd => (isString(cmd) ? cmd : isArray(cmd) ? cmd.join(' ') : '')).join(' ');
|
|
47
|
+
const message = [prefix, command, isCached ? '[cached]' : ''].join(' ').trim();
|
|
48
|
+
this.log(message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
obtrusive(...args) {
|
|
53
|
+
if (!this.isCI) this.log();
|
|
54
|
+
this.log(...args);
|
|
55
|
+
if (!this.isCI) this.log();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
preview({ title, text }) {
|
|
59
|
+
if (text) {
|
|
60
|
+
const header = chalk.bold(upperFirst(title));
|
|
61
|
+
const body = text.replace(new RegExp(EOL + EOL, 'g'), EOL);
|
|
62
|
+
this.obtrusive(`${header}:${EOL}${body}`);
|
|
63
|
+
} else {
|
|
64
|
+
this.obtrusive(`Empty ${lowerCase(title)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default Logger;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { EOL } from 'node:os';
|
|
2
|
+
import { format, parseGitUrl } from '../util.js';
|
|
3
|
+
import Plugin from './Plugin.js';
|
|
4
|
+
|
|
5
|
+
const options = { write: false };
|
|
6
|
+
const changelogFallback = 'git log --pretty=format:"* %s (%h)"';
|
|
7
|
+
|
|
8
|
+
class GitBase extends Plugin {
|
|
9
|
+
async init() {
|
|
10
|
+
const remoteUrl = await this.getRemoteUrl();
|
|
11
|
+
await this.fetch(remoteUrl);
|
|
12
|
+
|
|
13
|
+
const branchName = await this.getBranchName();
|
|
14
|
+
const repo = parseGitUrl(remoteUrl);
|
|
15
|
+
this.setContext({ remoteUrl, branchName, repo });
|
|
16
|
+
this.config.setContext({ remoteUrl, branchName, repo });
|
|
17
|
+
|
|
18
|
+
const latestTag = await this.getLatestTagName();
|
|
19
|
+
const secondLatestTag = !this.config.isIncrement ? await this.getSecondLatestTagName(latestTag) : null;
|
|
20
|
+
const tagTemplate = this.options.tagName || ((latestTag || '').match(/^v/) ? 'v${version}' : '${version}');
|
|
21
|
+
this.config.setContext({ latestTag, secondLatestTag, tagTemplate });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getName() {
|
|
25
|
+
return this.getContext('repo.project');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getLatestVersion() {
|
|
29
|
+
const { tagTemplate, latestTag } = this.config.getContext();
|
|
30
|
+
const prefix = tagTemplate.replace(/\$\{version\}/, '');
|
|
31
|
+
return latestTag ? latestTag.replace(prefix, '').replace(/^v/, '') : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getChangelog() {
|
|
35
|
+
const { snapshot } = this.config.getContext();
|
|
36
|
+
const { latestTag, secondLatestTag } = this.config.getContext();
|
|
37
|
+
const context = { latestTag, from: latestTag, to: 'HEAD' };
|
|
38
|
+
const { changelog } = this.options;
|
|
39
|
+
if (!changelog) return null;
|
|
40
|
+
|
|
41
|
+
if (latestTag && !this.config.isIncrement) {
|
|
42
|
+
context.from = secondLatestTag;
|
|
43
|
+
context.to = `${latestTag}^1`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// For now, snapshots do not get a changelog, as it often goes haywire (easy to add to release manually)
|
|
47
|
+
if (snapshot) return '';
|
|
48
|
+
|
|
49
|
+
if (!context.from && changelog.includes('${from}')) {
|
|
50
|
+
return this.exec(changelogFallback);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return this.exec(changelog, { context, options });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
bump(version) {
|
|
57
|
+
const { tagTemplate } = this.config.getContext();
|
|
58
|
+
const context = Object.assign(this.config.getContext(), { version });
|
|
59
|
+
const tagName = format(tagTemplate, context) || version;
|
|
60
|
+
this.setContext({ version });
|
|
61
|
+
this.config.setContext({ tagName });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isRemoteName(remoteUrlOrName) {
|
|
65
|
+
return remoteUrlOrName && !remoteUrlOrName.includes('/');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async getRemoteUrl() {
|
|
69
|
+
const remoteNameOrUrl = this.options.pushRepo || (await this.getRemote()) || 'origin';
|
|
70
|
+
return this.isRemoteName(remoteNameOrUrl)
|
|
71
|
+
? this.exec(`git remote get-url ${remoteNameOrUrl}`, { options }).catch(() =>
|
|
72
|
+
this.exec(`git config --get remote.${remoteNameOrUrl}.url`, { options }).catch(() => null)
|
|
73
|
+
)
|
|
74
|
+
: remoteNameOrUrl;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async getRemote() {
|
|
78
|
+
const branchName = await this.getBranchName();
|
|
79
|
+
return branchName ? await this.getRemoteForBranch(branchName) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getBranchName() {
|
|
83
|
+
return this.exec('git rev-parse --abbrev-ref HEAD', { options }).catch(() => null);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getRemoteForBranch(branch) {
|
|
87
|
+
return this.exec(`git config --get branch.${branch}.remote`, { options }).catch(() => null);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fetch(remoteUrl) {
|
|
91
|
+
return this.exec('git fetch').catch(err => {
|
|
92
|
+
this.debug(err);
|
|
93
|
+
throw new Error(`Unable to fetch from ${remoteUrl}${EOL}${err.message}`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getLatestTagName() {
|
|
98
|
+
const context = Object.assign({}, this.config.getContext(), { version: '*' });
|
|
99
|
+
const match = format(this.options.tagMatch || this.options.tagName || '${version}', context);
|
|
100
|
+
const exclude = this.options.tagExclude ? ` --exclude=${format(this.options.tagExclude, context)}` : '';
|
|
101
|
+
if (this.options.getLatestTagFromAllRefs) {
|
|
102
|
+
return this.exec(
|
|
103
|
+
`git -c "versionsort.suffix=-" for-each-ref --count=1 --sort=-v:refname --format="%(refname:short)" refs/tags/${match}`,
|
|
104
|
+
{ options }
|
|
105
|
+
).then(
|
|
106
|
+
stdout => stdout || null,
|
|
107
|
+
() => null
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
return this.exec(`git describe --tags --match=${match} --abbrev=0${exclude}`, { options }).then(
|
|
111
|
+
stdout => stdout || null,
|
|
112
|
+
() => null
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getSecondLatestTagName(latestTag) {
|
|
118
|
+
const sha = await this.exec(`git rev-list ${latestTag || '--skip=1'} --tags --max-count=1`, {
|
|
119
|
+
options
|
|
120
|
+
});
|
|
121
|
+
return this.exec(`git describe --tags --abbrev=0 "${sha}^"`, { options }).catch(() => null);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default GitBase;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { readJSON } from '../util.js';
|
|
3
|
+
import GitBase from './GitBase.js';
|
|
4
|
+
|
|
5
|
+
const defaultConfig = readJSON(new URL('../../config/release-it.json', import.meta.url));
|
|
6
|
+
|
|
7
|
+
class GitRelease extends GitBase {
|
|
8
|
+
static isEnabled(options) {
|
|
9
|
+
return options.release;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getInitialOptions(options) {
|
|
13
|
+
const baseOptions = super.getInitialOptions(...arguments);
|
|
14
|
+
const git = options.git || defaultConfig.git;
|
|
15
|
+
const gitOptions = _.pick(git, [
|
|
16
|
+
'tagExclude',
|
|
17
|
+
'tagName',
|
|
18
|
+
'tagMatch',
|
|
19
|
+
'getLatestTagFromAllRefs',
|
|
20
|
+
'pushRepo',
|
|
21
|
+
'changelog'
|
|
22
|
+
]);
|
|
23
|
+
return _.defaults(baseOptions, gitOptions);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get token() {
|
|
27
|
+
const { tokenRef } = this.options;
|
|
28
|
+
return _.get(process.env, tokenRef, null);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async beforeRelease() {
|
|
32
|
+
const { releaseNotes: script } = this.options;
|
|
33
|
+
const { changelog } = this.config.getContext();
|
|
34
|
+
const releaseNotes = script ? await this.processReleaseNotes(script) : changelog;
|
|
35
|
+
this.setContext({ releaseNotes });
|
|
36
|
+
if (releaseNotes !== changelog) {
|
|
37
|
+
this.log.preview({ title: 'release notes', text: releaseNotes });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async processReleaseNotes(script) {
|
|
42
|
+
if (typeof script === 'function') {
|
|
43
|
+
const ctx = Object.assign({}, this.config.getContext(), { [this.namespace]: this.getContext() });
|
|
44
|
+
return script(ctx);
|
|
45
|
+
} else {
|
|
46
|
+
return this.exec(script);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
afterRelease() {
|
|
51
|
+
const { isReleased, releaseUrl } = this.getContext();
|
|
52
|
+
if (isReleased) {
|
|
53
|
+
this.log.log(`🔗 ${releaseUrl}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default GitRelease;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { debug } from 'node:util';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
|
|
4
|
+
class Plugin {
|
|
5
|
+
static isEnabled() {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static disablePlugin() {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
constructor({ namespace, options = {}, container = {} } = {}) {
|
|
14
|
+
this.namespace = namespace;
|
|
15
|
+
this.options = Object.freeze(this.getInitialOptions(options, namespace));
|
|
16
|
+
this.context = {};
|
|
17
|
+
this.config = container.config;
|
|
18
|
+
this.log = container.log;
|
|
19
|
+
this.shell = container.shell;
|
|
20
|
+
this.spinner = container.spinner;
|
|
21
|
+
this.prompt = container.prompt;
|
|
22
|
+
this.debug = debug(`release-it:${namespace}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getInitialOptions(options, namespace) {
|
|
26
|
+
return options[namespace] || {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
init() {}
|
|
30
|
+
getName() {}
|
|
31
|
+
getLatestVersion() {}
|
|
32
|
+
getChangelog() {}
|
|
33
|
+
getIncrement() {}
|
|
34
|
+
getIncrementedVersionCI() {}
|
|
35
|
+
getIncrementedVersion() {}
|
|
36
|
+
beforeBump() {}
|
|
37
|
+
bump() {}
|
|
38
|
+
beforeRelease() {}
|
|
39
|
+
release() {}
|
|
40
|
+
afterRelease() {}
|
|
41
|
+
|
|
42
|
+
getContext(path) {
|
|
43
|
+
const context = _.merge({}, this.options, this.context);
|
|
44
|
+
return path ? _.get(context, path) : context;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setContext(context) {
|
|
48
|
+
_.merge(this.context, context);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exec(command, { options, context = {} } = {}) {
|
|
52
|
+
const ctx = Object.assign(context, this.config.getContext(), { [this.namespace]: this.getContext() });
|
|
53
|
+
return this.shell.exec(command, options, ctx);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
registerPrompts(prompts) {
|
|
57
|
+
this.prompt.register(prompts, this.namespace);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async showPrompt(options) {
|
|
61
|
+
options.namespace = this.namespace;
|
|
62
|
+
return this.prompt.show(options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
step(options) {
|
|
66
|
+
const context = Object.assign({}, this.config.getContext(), { [this.namespace]: this.getContext() });
|
|
67
|
+
const opts = Object.assign({}, options, { context });
|
|
68
|
+
const isException = this.config.isPromptOnlyVersion && ['incrementList', 'publish', 'otp'].includes(opts.prompt);
|
|
69
|
+
return this.config.isCI && !isException ? this.spinner.show(opts) : this.showPrompt(opts);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default Plugin;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import url from 'node:url';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import util from 'node:util';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
import Version from './version/Version.js';
|
|
7
|
+
import Git from './git/Git.js';
|
|
8
|
+
import GitLab from './gitlab/GitLab.js';
|
|
9
|
+
import GitHub from './github/GitHub.js';
|
|
10
|
+
import npm from './npm/npm.js';
|
|
11
|
+
|
|
12
|
+
const debug = util.debug('release-it:plugins');
|
|
13
|
+
|
|
14
|
+
const pluginNames = ['npm', 'git', 'github', 'gitlab', 'version'];
|
|
15
|
+
|
|
16
|
+
const plugins = {
|
|
17
|
+
version: Version,
|
|
18
|
+
git: Git,
|
|
19
|
+
gitlab: GitLab,
|
|
20
|
+
github: GitHub,
|
|
21
|
+
npm: npm
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const load = async pluginName => {
|
|
25
|
+
let plugin = null;
|
|
26
|
+
try {
|
|
27
|
+
const module = await import(pluginName);
|
|
28
|
+
plugin = module.default;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
debug(err);
|
|
31
|
+
try {
|
|
32
|
+
const module = await import(path.join(process.cwd(), pluginName));
|
|
33
|
+
plugin = module.default;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
debug(err);
|
|
36
|
+
// In some cases or tests we might need to support legacy `require.resolve`
|
|
37
|
+
const require = createRequire(process.cwd());
|
|
38
|
+
const module = await import(url.pathToFileURL(require.resolve(pluginName, { paths: [process.cwd()] })));
|
|
39
|
+
plugin = module.default;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return [getPluginName(pluginName), plugin];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** @internal */
|
|
46
|
+
export const getPluginName = pluginName => {
|
|
47
|
+
if (pluginName.startsWith('.')) {
|
|
48
|
+
return path.parse(pluginName).name;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return pluginName;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export let getPlugins = async (config, container) => {
|
|
55
|
+
const context = config.getContext();
|
|
56
|
+
const disabledPlugins = [];
|
|
57
|
+
|
|
58
|
+
const enabledExternalPlugins = await _.reduce(
|
|
59
|
+
context.plugins,
|
|
60
|
+
async (result, pluginConfig, pluginName) => {
|
|
61
|
+
const [name, Plugin] = await load(pluginName);
|
|
62
|
+
const [namespace, options] = pluginConfig.length === 2 ? pluginConfig : [name, pluginConfig];
|
|
63
|
+
config.setContext({ [namespace]: options });
|
|
64
|
+
if (await Plugin.isEnabled(options)) {
|
|
65
|
+
const instance = new Plugin({ namespace, options: config.getContext(), container });
|
|
66
|
+
debug({ namespace, options: instance.options });
|
|
67
|
+
(await result).push(instance);
|
|
68
|
+
disabledPlugins.push(..._.intersection(pluginNames, _.castArray(Plugin.disablePlugin(options))));
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
},
|
|
72
|
+
[]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const enabledPlugins = await pluginNames.reduce(async (result, plugin) => {
|
|
76
|
+
if (plugin in plugins && !disabledPlugins.includes(plugin)) {
|
|
77
|
+
const Plugin = plugins[plugin];
|
|
78
|
+
const pluginOptions = context[plugin];
|
|
79
|
+
if (await Plugin.isEnabled(pluginOptions)) {
|
|
80
|
+
const instance = new Plugin({ namespace: plugin, options: context, container });
|
|
81
|
+
debug({ namespace: plugin, options: instance.options });
|
|
82
|
+
(await result).push(instance);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
return [enabledPlugins, enabledExternalPlugins];
|
|
89
|
+
};
|