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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +421 -0
  3. package/bin/release-it.js +42 -0
  4. package/config/release-it.json +70 -0
  5. package/lib/cli.js +44 -0
  6. package/lib/config.js +139 -0
  7. package/lib/index.js +152 -0
  8. package/lib/log.js +69 -0
  9. package/lib/plugin/GitBase.js +125 -0
  10. package/lib/plugin/GitRelease.js +58 -0
  11. package/lib/plugin/Plugin.js +73 -0
  12. package/lib/plugin/factory.js +89 -0
  13. package/lib/plugin/git/Git.js +220 -0
  14. package/lib/plugin/git/prompts.js +19 -0
  15. package/lib/plugin/github/GitHub.js +403 -0
  16. package/lib/plugin/github/prompts.js +16 -0
  17. package/lib/plugin/github/util.js +39 -0
  18. package/lib/plugin/gitlab/GitLab.js +277 -0
  19. package/lib/plugin/gitlab/prompts.js +9 -0
  20. package/lib/plugin/npm/npm.js +281 -0
  21. package/lib/plugin/npm/prompts.js +12 -0
  22. package/lib/plugin/version/Version.js +129 -0
  23. package/lib/prompt.js +33 -0
  24. package/lib/shell.js +91 -0
  25. package/lib/spinner.js +29 -0
  26. package/lib/util.js +109 -0
  27. package/package.json +122 -0
  28. package/test/cli.js +20 -0
  29. package/test/config.js +144 -0
  30. package/test/git.init.js +250 -0
  31. package/test/git.js +358 -0
  32. package/test/github.js +487 -0
  33. package/test/gitlab.js +252 -0
  34. package/test/log.js +143 -0
  35. package/test/npm.js +417 -0
  36. package/test/plugin-name.js +9 -0
  37. package/test/plugins.js +238 -0
  38. package/test/prompt.js +97 -0
  39. package/test/resources/file-v2.0.1.txt +1 -0
  40. package/test/resources/file-v2.0.2.txt +1 -0
  41. package/test/resources/file1 +1 -0
  42. package/test/shell.js +74 -0
  43. package/test/spinner.js +58 -0
  44. package/test/stub/config/default/.release-it.json +5 -0
  45. package/test/stub/config/invalid-config-rc +1 -0
  46. package/test/stub/config/invalid-config-txt +2 -0
  47. package/test/stub/config/merge/.release-it.json +5 -0
  48. package/test/stub/config/merge/package.json +7 -0
  49. package/test/stub/config/toml/.release-it.toml +2 -0
  50. package/test/stub/config/yaml/.release-it.yaml +2 -0
  51. package/test/stub/config/yml/.release-it.yml +2 -0
  52. package/test/stub/github.js +130 -0
  53. package/test/stub/gitlab.js +44 -0
  54. package/test/stub/plugin-context.js +36 -0
  55. package/test/stub/plugin-replace.js +9 -0
  56. package/test/stub/plugin.js +39 -0
  57. package/test/stub/shell.js +24 -0
  58. package/test/tasks.interactive.js +208 -0
  59. package/test/tasks.js +585 -0
  60. package/test/util/helpers.js +32 -0
  61. package/test/util/index.js +78 -0
  62. package/test/util/setup.js +5 -0
  63. package/test/utils.js +97 -0
  64. package/test/version.js +173 -0
package/lib/prompt.js ADDED
@@ -0,0 +1,33 @@
1
+ import inquirer from 'inquirer';
2
+
3
+ class Prompt {
4
+ constructor({ container }) {
5
+ this.createPrompt = (container.inquirer || inquirer).prompt;
6
+ this.prompts = {};
7
+ }
8
+
9
+ register(pluginPrompts, namespace = 'default') {
10
+ this.prompts[namespace] = this.prompts[namespace] || {};
11
+ Object.assign(this.prompts[namespace], pluginPrompts);
12
+ }
13
+
14
+ async show({ enabled = true, prompt: promptName, namespace = 'default', task, context }) {
15
+ if (!enabled) return false;
16
+
17
+ const prompt = this.prompts[namespace][promptName];
18
+ const options = Object.assign({}, prompt, {
19
+ name: promptName,
20
+ message: prompt.message(context),
21
+ choices: 'choices' in prompt && prompt.choices(context),
22
+ transformer: 'transformer' in prompt && prompt.transformer(context)
23
+ });
24
+
25
+ const answers = await this.createPrompt([options]);
26
+
27
+ const doExecute = prompt.type === 'confirm' ? answers[promptName] : true;
28
+
29
+ return doExecute && task ? await task(answers[promptName]) : false;
30
+ }
31
+ }
32
+
33
+ export default Prompt;
package/lib/shell.js ADDED
@@ -0,0 +1,91 @@
1
+ import util from 'node:util';
2
+ import sh from 'shelljs';
3
+ import { execa } from 'execa';
4
+ import { format } from './util.js';
5
+
6
+ const debug = util.debug('release-it:shell');
7
+
8
+ sh.config.silent = !debug.enabled;
9
+
10
+ const noop = Promise.resolve();
11
+
12
+ class Shell {
13
+ constructor({ container }) {
14
+ this.log = container.log;
15
+ this.config = container.config;
16
+ this.cache = new Map();
17
+ }
18
+
19
+ exec(command, options = {}, context = {}) {
20
+ if (!command || !command.length) return;
21
+ return typeof command === 'string'
22
+ ? this.execFormattedCommand(format(command, context), options)
23
+ : this.execFormattedCommand(command, options);
24
+ }
25
+
26
+ async execFormattedCommand(command, options = {}) {
27
+ const { isDryRun } = this.config;
28
+ const isWrite = options.write !== false;
29
+ const isExternal = options.external === true;
30
+ const cacheKey = typeof command === 'string' ? command : command.join(' ');
31
+ const isCached = !isExternal && this.cache.has(cacheKey);
32
+
33
+ if (isDryRun && isWrite) {
34
+ this.log.exec(command, { isDryRun });
35
+ return noop;
36
+ }
37
+
38
+ this.log.exec(command, { isExternal, isCached });
39
+
40
+ if (isCached) {
41
+ return this.cache.get(cacheKey);
42
+ }
43
+
44
+ const result =
45
+ typeof command === 'string'
46
+ ? this.execStringCommand(command, options, { isExternal })
47
+ : this.execWithArguments(command, options, { isExternal });
48
+
49
+ if (!isExternal && !this.cache.has(cacheKey)) {
50
+ this.cache.set(cacheKey, result);
51
+ }
52
+
53
+ return result;
54
+ }
55
+
56
+ execStringCommand(command, options, { isExternal }) {
57
+ return new Promise((resolve, reject) => {
58
+ const childProcess = sh.exec(command, { async: true }, (code, stdout, stderr) => {
59
+ stdout = stdout.toString().trimEnd();
60
+ debug({ command, options, code, stdout, stderr });
61
+ if (code === 0) {
62
+ resolve(stdout);
63
+ } else {
64
+ reject(new Error(stderr || stdout));
65
+ }
66
+ });
67
+ childProcess.stdout.on('data', stdout => this.log.verbose(stdout.toString().trimEnd(), { isExternal }));
68
+ childProcess.stderr.on('data', stderr => this.log.verbose(stderr.toString().trimEnd(), { isExternal }));
69
+ });
70
+ }
71
+
72
+ async execWithArguments(command, options, { isExternal }) {
73
+ const [program, ...programArgs] = command;
74
+
75
+ try {
76
+ const { stdout: out, stderr } = await execa(program, programArgs);
77
+ const stdout = out === '""' ? '' : out;
78
+ this.log.verbose(stdout, { isExternal });
79
+ debug({ command, options, stdout, stderr });
80
+ return Promise.resolve(stdout || stderr);
81
+ } catch (error) {
82
+ if (error.stdout) {
83
+ this.log.log(`\n${error.stdout}`);
84
+ }
85
+ debug({ error });
86
+ return Promise.reject(new Error(error.stderr || error.message));
87
+ }
88
+ }
89
+ }
90
+
91
+ export default Shell;
package/lib/spinner.js ADDED
@@ -0,0 +1,29 @@
1
+ import { oraPromise } from 'ora';
2
+ import { format } from './util.js';
3
+
4
+ const noop = Promise.resolve();
5
+
6
+ class Spinner {
7
+ constructor({ container = {} } = {}) {
8
+ this.config = container.config;
9
+ this.ora = container.ora || oraPromise;
10
+ }
11
+ show({ enabled = true, task, label, external = false, context }) {
12
+ if (!enabled) return noop;
13
+
14
+ const { config } = this;
15
+ this.isSpinnerDisabled = !config.isCI || config.isVerbose || config.isDryRun || config.isDebug;
16
+ this.canForce = !config.isCI && !config.isVerbose && !config.isDryRun && !config.isDebug;
17
+
18
+ const awaitTask = task();
19
+
20
+ if (!this.isSpinnerDisabled || (external && this.canForce)) {
21
+ const text = format(label, context);
22
+ this.ora(awaitTask, text);
23
+ }
24
+
25
+ return awaitTask;
26
+ }
27
+ }
28
+
29
+ export default Spinner;
package/lib/util.js ADDED
@@ -0,0 +1,109 @@
1
+ import fs from 'node:fs';
2
+ import { EOL } from 'node:os';
3
+ import _ from 'lodash';
4
+ import gitUrlParse from 'git-url-parse';
5
+ import semver from 'semver';
6
+ import osName from 'os-name';
7
+ import Log from './log.js';
8
+
9
+ const readJSON = file => JSON.parse(fs.readFileSync(file, 'utf8'));
10
+
11
+ const pkg = readJSON(new URL('../package.json', import.meta.url));
12
+
13
+ const log = new Log();
14
+
15
+ const getSystemInfo = () => {
16
+ return {
17
+ 'release-it': pkg.version,
18
+ node: process.version,
19
+ os: osName()
20
+ };
21
+ };
22
+
23
+ const format = (template = '', context = {}) => {
24
+ try {
25
+ return _.template(template)(context);
26
+ } catch (error) {
27
+ log.error(`Unable to render template with context:\n${template}\n${JSON.stringify(context)}`);
28
+ log.error(error);
29
+ throw error;
30
+ }
31
+ };
32
+
33
+ const truncateLines = (input, maxLines = 10, surplusText = null) => {
34
+ const lines = input.split(EOL);
35
+ const surplus = lines.length - maxLines;
36
+ const output = lines.slice(0, maxLines).join(EOL);
37
+ return surplus > 0 ? (surplusText ? `${output}${surplusText}` : `${output}${EOL}...and ${surplus} more`) : output;
38
+ };
39
+
40
+ const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
41
+
42
+ const rejectAfter = (ms, error) =>
43
+ wait(ms).then(() => {
44
+ throw error;
45
+ });
46
+
47
+ const parseGitUrl = remoteUrl => {
48
+ if (!remoteUrl) return { host: null, owner: null, project: null, protocol: null, remote: null, repository: null };
49
+ const normalizedUrl = (remoteUrl || '')
50
+ .replace(/^[A-Z]:\\\\/, 'file://') // Assume file protocol for Windows drive letters
51
+ .replace(/^\//, 'file://') // Assume file protocol if only /path is given
52
+ .replace(/\\+/g, '/'); // Replace forward with backslashes
53
+ const parsedUrl = gitUrlParse(normalizedUrl);
54
+ const { resource: host, name: project, protocol, href: remote } = parsedUrl;
55
+ const owner = protocol === 'file' ? _.last(parsedUrl.owner.split('/')) : parsedUrl.owner; // Fix owner for file protocol
56
+ const repository = `${owner}/${project}`;
57
+ return { host, owner, project, protocol, remote, repository };
58
+ };
59
+
60
+ const reduceUntil = async (collection, fn) => {
61
+ let result;
62
+ for (const item of collection) {
63
+ if (result) break;
64
+ result = await fn(item);
65
+ }
66
+ return result;
67
+ };
68
+
69
+ const hasAccess = path => {
70
+ try {
71
+ fs.accessSync(path);
72
+ return true;
73
+ } catch (err) {
74
+ return false;
75
+ }
76
+ };
77
+
78
+ const parseVersion = raw => {
79
+ if (raw == null) return { version: raw, isPreRelease: false, preReleaseId: null };
80
+ const version = semver.valid(raw) ? raw : semver.coerce(raw);
81
+ if (!version) return { version: raw, isPreRelease: false, preReleaseId: null };
82
+ const parsed = semver.parse(version);
83
+ const isPreRelease = parsed.prerelease.length > 0;
84
+ const preReleaseId = isPreRelease && isNaN(parsed.prerelease[0]) ? parsed.prerelease[0] : null;
85
+ return {
86
+ version: version.toString(),
87
+ isPreRelease,
88
+ preReleaseId
89
+ };
90
+ };
91
+
92
+ const e = (message, docs, fail = true) => {
93
+ const error = new Error(docs ? `${message}${EOL}Documentation: ${docs}${EOL}` : message);
94
+ error.code = fail ? 1 : 0;
95
+ return error;
96
+ };
97
+
98
+ export {
99
+ getSystemInfo,
100
+ format,
101
+ truncateLines,
102
+ rejectAfter,
103
+ reduceUntil,
104
+ parseGitUrl,
105
+ hasAccess,
106
+ parseVersion,
107
+ readJSON,
108
+ e
109
+ };
package/package.json ADDED
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "release-it",
3
+ "version": "0.0.0-pl.0",
4
+ "description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
5
+ "keywords": [
6
+ "build",
7
+ "changelog",
8
+ "commit",
9
+ "distribution",
10
+ "git",
11
+ "github",
12
+ "gitlab",
13
+ "interactive",
14
+ "ci",
15
+ "npm",
16
+ "publish",
17
+ "push",
18
+ "release",
19
+ "release-it",
20
+ "repository",
21
+ "script",
22
+ "shell",
23
+ "tag",
24
+ "tool",
25
+ "version",
26
+ "semver",
27
+ "plugin"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/release-it/release-it.git"
32
+ },
33
+ "homepage": "https://github.com/release-it/release-it#readme",
34
+ "bugs": "https://github.com/release-it/release-it/issues",
35
+ "bin": {
36
+ "release-it": "bin/release-it.js"
37
+ },
38
+ "type": "module",
39
+ "exports": {
40
+ ".": "./lib/index.js",
41
+ "./package.json": "./package.json",
42
+ "./test/util/index.js": "./test/util/index.js"
43
+ },
44
+ "files": [
45
+ "bin",
46
+ "config",
47
+ "lib",
48
+ "test"
49
+ ],
50
+ "scripts": {
51
+ "knip": "knip",
52
+ "lint": "eslint lib test",
53
+ "format": "prettier --write \"{lib,test}/**/*.js\"",
54
+ "docs": "remark README.md 'docs/**/*.md' '.github/*.md' -o",
55
+ "test": "ava --no-worker-threads",
56
+ "release": "./bin/release-it.js"
57
+ },
58
+ "author": {
59
+ "email": "lars@webpro.nl",
60
+ "name": "Lars Kappert"
61
+ },
62
+ "license": "MIT",
63
+ "dependencies": {
64
+ "@iarna/toml": "2.2.5",
65
+ "@octokit/rest": "19.0.13",
66
+ "async-retry": "1.3.3",
67
+ "chalk": "5.3.0",
68
+ "cosmiconfig": "8.3.6",
69
+ "execa": "7.2.0",
70
+ "git-url-parse": "13.1.0",
71
+ "globby": "13.2.2",
72
+ "got": "13.0.0",
73
+ "inquirer": "9.2.11",
74
+ "is-ci": "3.0.1",
75
+ "issue-parser": "6.0.0",
76
+ "lodash": "4.17.21",
77
+ "mime-types": "2.1.35",
78
+ "new-github-release-url": "2.0.0",
79
+ "node-fetch": "3.3.2",
80
+ "open": "9.1.0",
81
+ "ora": "7.0.1",
82
+ "os-name": "5.1.0",
83
+ "promise.allsettled": "1.0.7",
84
+ "proxy-agent": "6.3.1",
85
+ "semver": "7.5.4",
86
+ "shelljs": "0.8.5",
87
+ "update-notifier": "6.0.2",
88
+ "url-join": "5.0.0",
89
+ "wildcard-match": "5.1.2",
90
+ "yargs-parser": "21.1.1"
91
+ },
92
+ "devDependencies": {
93
+ "@octokit/request-error": "3.0.3",
94
+ "ava": "5.3.1",
95
+ "eslint": "8.50.0",
96
+ "eslint-config-prettier": "9.0.0",
97
+ "eslint-plugin-ava": "14.0.0",
98
+ "eslint-plugin-import": "2.28.1",
99
+ "eslint-plugin-prettier": "5.0.0",
100
+ "fs-monkey": "1.0.5",
101
+ "knip": "2.29.0",
102
+ "memfs": "4.5.0",
103
+ "mock-stdio": "1.0.3",
104
+ "nock": "13.3.3",
105
+ "prettier": "3.0.3",
106
+ "remark-cli": "11.0.0",
107
+ "remark-preset-webpro": "0.0.3",
108
+ "sinon": "16.0.0",
109
+ "strip-ansi": "7.1.0"
110
+ },
111
+ "overrides": {
112
+ "@octokit/plugin-rest-endpoint-methods": "7.2.2"
113
+ },
114
+ "engines": {
115
+ "node": ">=16"
116
+ },
117
+ "remarkConfig": {
118
+ "plugins": [
119
+ "preset-webpro"
120
+ ]
121
+ }
122
+ }
package/test/cli.js ADDED
@@ -0,0 +1,20 @@
1
+ import test from 'ava';
2
+ import mockStdIo from 'mock-stdio';
3
+ import { version, help } from '../lib/cli.js';
4
+ import { readJSON } from '../lib/util.js';
5
+
6
+ const pkg = readJSON(new URL('../package.json', import.meta.url));
7
+
8
+ test('should print version', t => {
9
+ mockStdIo.start();
10
+ version();
11
+ const { stdout } = mockStdIo.end();
12
+ t.is(stdout, `v${pkg.version}\n`);
13
+ });
14
+
15
+ test('should print help', t => {
16
+ mockStdIo.start();
17
+ help();
18
+ const { stdout } = mockStdIo.end();
19
+ t.regex(stdout, RegExp(`Release It!.+${pkg.version}`));
20
+ });
package/test/config.js ADDED
@@ -0,0 +1,144 @@
1
+ import test from 'ava';
2
+ import isCI from 'is-ci';
3
+ import Config from '../lib/config.js';
4
+ import { readJSON } from '../lib/util.js';
5
+
6
+ const defaultConfig = readJSON(new URL('../config/release-it.json', import.meta.url));
7
+ const projectConfig = readJSON(new URL('../.release-it.json', import.meta.url));
8
+
9
+ const localConfig = { github: { release: true } };
10
+
11
+ test("should read this project's own configuration", t => {
12
+ const config = new Config();
13
+ t.deepEqual(config.constructorConfig, {});
14
+ t.deepEqual(config.localConfig, projectConfig);
15
+ t.deepEqual(config.defaultConfig, defaultConfig);
16
+ });
17
+
18
+ test('should contain default values', t => {
19
+ const config = new Config({ configDir: './test/stub/config/default' });
20
+ t.deepEqual(config.constructorConfig, { configDir: './test/stub/config/default' });
21
+ t.deepEqual(config.localConfig, localConfig);
22
+ t.deepEqual(config.defaultConfig, defaultConfig);
23
+ });
24
+
25
+ test('should merge provided options', t => {
26
+ const config = new Config({
27
+ configDir: './test/stub/config/merge',
28
+ increment: '1.0.0',
29
+ verbose: true,
30
+ github: {
31
+ release: true
32
+ }
33
+ });
34
+ const { options } = config;
35
+ t.is(config.isVerbose, true);
36
+ t.is(config.isDryRun, false);
37
+ t.is(options.increment, '1.0.0');
38
+ t.is(options.git.push, false);
39
+ t.is(options.github.release, true);
40
+ });
41
+
42
+ test('should set CI mode', t => {
43
+ const config = new Config({ ci: true });
44
+ t.is(config.isCI, true);
45
+ });
46
+
47
+ test('should detect CI mode', t => {
48
+ const config = new Config();
49
+ t.is(config.options.ci, isCI);
50
+ t.is(config.isCI, isCI);
51
+ });
52
+
53
+ test('should override --no-npm.publish', t => {
54
+ const config = new Config({ npm: { publish: false } });
55
+ t.is(config.options.npm.publish, false);
56
+ });
57
+
58
+ test('should read YAML config', t => {
59
+ const config = new Config({ configDir: './test/stub/config/yaml' });
60
+ t.deepEqual(config.options.foo, { bar: 1 });
61
+ });
62
+
63
+ test('should read YML config', t => {
64
+ const config = new Config({ configDir: './test/stub/config/yml' });
65
+ t.deepEqual(config.options.foo, { bar: 1 });
66
+ });
67
+
68
+ test('should read TOML config', t => {
69
+ const config = new Config({ configDir: './test/stub/config/toml' });
70
+ t.deepEqual(config.options.foo, { bar: 1 });
71
+ });
72
+
73
+ test('should throw if provided config file is not found', t => {
74
+ t.throws(() => new Config({ config: 'nofile' }), { message: /no such file.+nofile/ });
75
+ });
76
+
77
+ test('should throw if provided config file is invalid (cosmiconfig exception)', t => {
78
+ t.throws(() => new Config({ config: './test/stub/config/invalid-config-txt' }), {
79
+ message: /Invalid configuration file at/
80
+ });
81
+ });
82
+
83
+ test('should throw if provided config file is invalid (no object)', t => {
84
+ t.throws(() => new Config({ config: './test/stub/config/invalid-config-rc' }), {
85
+ message: /Invalid configuration file at/
86
+ });
87
+ });
88
+
89
+ test('should not set default increment (for CI mode)', t => {
90
+ const config = new Config({ ci: true });
91
+ t.is(config.options.version.increment, undefined);
92
+ });
93
+
94
+ test('should not set default increment (for interactive mode)', t => {
95
+ const config = new Config({ ci: false });
96
+ t.is(config.options.version.increment, undefined);
97
+ });
98
+
99
+ test('should expand pre-release shortcut', t => {
100
+ const config = new Config({ increment: 'major', preRelease: 'beta' });
101
+ t.deepEqual(config.options.version, {
102
+ increment: 'major',
103
+ isPreRelease: true,
104
+ preReleaseId: 'beta'
105
+ });
106
+ });
107
+
108
+ test('should expand pre-release shortcut (preRelease boolean)', t => {
109
+ const config = new Config({ ci: true, preRelease: true });
110
+ t.deepEqual(config.options.version, {
111
+ increment: undefined,
112
+ isPreRelease: true,
113
+ preReleaseId: undefined
114
+ });
115
+ });
116
+
117
+ test('should expand pre-release shortcut (without increment)', t => {
118
+ const config = new Config({ ci: false, preRelease: 'alpha' });
119
+ t.deepEqual(config.options.version, {
120
+ increment: undefined,
121
+ isPreRelease: true,
122
+ preReleaseId: 'alpha'
123
+ });
124
+ });
125
+
126
+ test('should expand pre-release shortcut (including increment and npm.tag)', t => {
127
+ const config = new Config({ increment: 'minor', preRelease: 'rc' });
128
+ t.deepEqual(config.options.version, {
129
+ increment: 'minor',
130
+ isPreRelease: true,
131
+ preReleaseId: 'rc'
132
+ });
133
+ });
134
+
135
+ test('should expand pre-release shortcut (snapshot)', t => {
136
+ const config = new Config({ snapshot: 'feat' });
137
+ t.deepEqual(config.options.version, {
138
+ increment: 'prerelease',
139
+ isPreRelease: true,
140
+ preReleaseId: 'feat'
141
+ });
142
+ t.is(config.options.git.tagMatch, '0.0.0-feat.[0-9]*');
143
+ t.true(config.options.git.getLatestTagFromAllRefs);
144
+ });