release-it 19.0.0-next.3 → 19.0.0-next.5

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/bin/release-it.js CHANGED
@@ -1,41 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import updater from 'update-notifier';
4
- import parseArgs from 'yargs-parser';
5
3
  import release from '../lib/cli.js';
6
- import { readJSON } from '../lib/util.js';
7
-
8
- const pkg = readJSON(new URL('../package.json', import.meta.url));
9
-
10
- const aliases = {
11
- c: 'config',
12
- d: 'dry-run',
13
- h: 'help',
14
- i: 'increment',
15
- v: 'version',
16
- V: 'verbose'
17
- };
18
-
19
- const parseCliArguments = args => {
20
- const options = parseArgs(args, {
21
- boolean: ['dry-run', 'ci'],
22
- alias: aliases,
23
- configuration: {
24
- 'parse-numbers': false,
25
- 'camel-case-expansion': false
26
- }
27
- });
28
- if (options.V) {
29
- options.verbose = typeof options.V === 'boolean' ? options.V : options.V.length;
30
- delete options.V;
31
- }
32
- options.increment = options._[0] || options.i;
33
- return options;
34
- };
4
+ import { parseCliArguments } from '../lib/args.js';
35
5
 
36
6
  const options = parseCliArguments([].slice.call(process.argv, 2));
37
7
 
38
- updater({ pkg: pkg }).notify();
39
8
  release(options).then(
40
9
  () => process.exit(0),
41
10
  ({ code }) => process.exit(Number.isInteger(code) ? code : 1)
package/lib/args.js ADDED
@@ -0,0 +1,64 @@
1
+ import parseArgs from 'yargs-parser';
2
+
3
+ const aliases = {
4
+ c: 'config',
5
+ d: 'dry-run',
6
+ h: 'help',
7
+ i: 'increment',
8
+ v: 'version',
9
+ V: 'verbose'
10
+ };
11
+
12
+ const booleanOptions = [
13
+ 'dry-run',
14
+ 'ci',
15
+ 'git',
16
+ 'npm',
17
+ 'github',
18
+ 'gitlab',
19
+ 'git.addUntrackedFiles',
20
+ 'git.requireCleanWorkingDir',
21
+ 'git.requireUpstream',
22
+ 'git.requireCommits',
23
+ 'git.requireCommitsFail',
24
+ 'git.commit',
25
+ 'git.tag',
26
+ 'git.push',
27
+ 'git.getLatestTagFromAllRefs',
28
+ 'git.skipChecks',
29
+ 'github.release',
30
+ 'github.autoGenerate',
31
+ 'github.preRelease',
32
+ 'github.draft',
33
+ 'github.skipChecks',
34
+ 'github.web',
35
+ 'github.comments.submit',
36
+ 'gitlab.release',
37
+ 'gitlab.autoGenerate',
38
+ 'gitlab.preRelease',
39
+ 'gitlab.draft',
40
+ 'gitlab.useIdsForUrls',
41
+ 'gitlab.useGenericPackageRepositoryForAssets',
42
+ 'gitlab.skipChecks',
43
+ 'npm.publish',
44
+ 'npm.ignoreVersion',
45
+ 'npm.allowSameVersion',
46
+ 'npm.skipChecks'
47
+ ];
48
+
49
+ export const parseCliArguments = args => {
50
+ const options = parseArgs(args, {
51
+ boolean: booleanOptions,
52
+ alias: aliases,
53
+ configuration: {
54
+ 'parse-numbers': false,
55
+ 'camel-case-expansion': false
56
+ }
57
+ });
58
+ if (options.V) {
59
+ options.verbose = typeof options.V === 'boolean' ? options.V : options.V.length;
60
+ delete options.V;
61
+ }
62
+ options.increment = options._[0] || options.i;
63
+ return options;
64
+ };
package/lib/config.js CHANGED
@@ -1,121 +1,31 @@
1
1
  import util from 'node:util';
2
- import { cosmiconfigSync } from 'cosmiconfig';
3
- import parseToml from '@iarna/toml/parse-string.js';
2
+ import assert from 'node:assert';
4
3
  import { isCI } from 'ci-info';
5
4
  import defaultsDeep from '@nodeutils/defaults-deep';
6
5
  import { isObjectStrict } from '@phun-ky/typeof';
7
6
  import merge from 'lodash.merge';
8
7
  import get from 'lodash.get';
9
- import { e, getSystemInfo, readJSON } from './util.js';
8
+ import { loadConfig as loadC12 } from 'c12';
9
+ import { getSystemInfo, readJSON } from './util.js';
10
10
 
11
11
  const debug = util.debug('release-it:config');
12
12
  const defaultConfig = readJSON(new URL('../config/release-it.json', import.meta.url));
13
13
 
14
- const searchPlaces = [
15
- 'package.json',
16
- '.release-it.json',
17
- '.release-it.js',
18
- '.release-it.ts',
19
- '.release-it.cjs',
20
- '.release-it.yaml',
21
- '.release-it.yml',
22
- '.release-it.toml'
23
- ];
24
-
25
- const loaders = {
26
- '.toml': (_, content) => parseToml(content)
27
- };
28
-
29
- const getLocalConfig = ({ file, dir = process.cwd() }) => {
30
- let localConfig = {};
31
- if (file === false) return localConfig;
32
- const explorer = cosmiconfigSync('release-it', {
33
- searchPlaces,
34
- loaders
35
- });
36
- const result = file ? explorer.load(file) : explorer.search(dir);
37
- if (result && typeof result.config === 'string') {
38
- throw new Error(`Invalid configuration file at ${result.filepath}`);
39
- }
40
- debug({ cosmiconfig: result });
41
- return result && isObjectStrict(result.config) ? result.config : localConfig;
42
- };
43
-
44
- const fetchConfigurationFromGitHub = async configuration => {
45
- const docs = 'https://github.com/release-it/release-it/blob/main/docs/configuration.md';
46
-
47
- const regex = /^github:([^/]+)\/([^#:]+)(?::([^#]+))?(?:#(.+))?$/;
48
- const match = configuration.match(regex);
49
-
50
- if (!match) {
51
- throw e(`Invalid Extended Configuration from GitHub: ${configuration}`, docs);
52
- }
53
-
54
- const [, owner, repo, file = '.release-it.json', tag] = match;
55
- const ref = tag ? `refs/tags/${tag}` : 'HEAD';
56
- const url = new URL(`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${file}`);
57
-
58
- const response = await fetch(url);
59
-
60
- if (!response.ok) {
61
- throw e(`Failed to fetch ${url}: ${response.statusText}`, docs);
62
- }
63
-
64
- return response.json();
65
- };
66
-
67
- const getRemoteConfiguration = async configuration => {
68
- return fetchConfigurationFromGitHub(configuration);
69
- };
70
-
71
14
  class Config {
72
15
  constructor(config = {}) {
73
16
  this.constructorConfig = config;
74
- this.localConfig = getLocalConfig({ file: config.config, dir: config.configDir });
75
- this.options = this.mergeOptions();
76
- this.options = this.expandPreReleaseShorthand(this.options);
77
17
  this.contextOptions = {};
78
18
  debug({ system: getSystemInfo() });
79
- debug(this.options);
80
- }
81
-
82
- expandPreReleaseShorthand(options) {
83
- const { increment, preRelease, preReleaseId, snapshot, preReleaseBase } = options;
84
- const isPreRelease = Boolean(preRelease) || Boolean(snapshot);
85
- const inc = snapshot ? 'prerelease' : increment;
86
- const preId = typeof preRelease === 'string' ? preRelease : typeof snapshot === 'string' ? snapshot : preReleaseId;
87
- options.version = {
88
- increment: inc,
89
- isPreRelease,
90
- preReleaseId: preId,
91
- preReleaseBase
92
- };
93
- if (typeof snapshot === 'string' && options.git) {
94
- // Pre set and hard code some options
95
- options.git.tagMatch = `0.0.0-${snapshot}.[0-9]*`;
96
- options.git.getLatestTagFromAllRefs = true;
97
- options.git.requireBranch = '!main';
98
- options.git.requireUpstream = false;
99
- options.npm.ignoreVersion = true;
100
- }
101
- return options;
102
- }
103
-
104
- mergeOptions() {
105
- return defaultsDeep(
106
- {},
107
- this.constructorConfig,
108
- {
109
- ci: isCI
110
- },
111
- this.localConfig,
112
- this.defaultConfig
113
- );
114
- }
115
-
116
- mergeRemoteOptions(remoteConfiguration) {
117
- return merge({}, this.options, remoteConfiguration);
118
19
  }
20
+
21
+ async init() {
22
+ await loadOptions(this.constructorConfig).then(({ options, localConfig }) => {
23
+ this._options = options;
24
+ this._localConfig = localConfig;
25
+ debug(this._options);
26
+ });
27
+ }
28
+
119
29
  getContext(path) {
120
30
  const context = merge({}, this.options, this.contextOptions);
121
31
  return path ? get(context, path) : context;
@@ -169,8 +79,108 @@ class Config {
169
79
  get isChangelog() {
170
80
  return Boolean(this.options['changelog']);
171
81
  }
82
+
83
+ get options() {
84
+ assert(this._options, `The "options" not resolve yet`);
85
+ return this._options;
86
+ }
87
+
88
+ get localConfig() {
89
+ assert(this._localConfig, `The "localConfig" not resolve yet`);
90
+ return this._localConfig;
91
+ }
172
92
  }
173
93
 
174
- export { getRemoteConfiguration };
94
+ async function loadOptions(constructorConfig) {
95
+ const localConfig = await loadLocalConfig(constructorConfig);
96
+ const merged = defaultsDeep(
97
+ {},
98
+ constructorConfig,
99
+ {
100
+ ci: isCI
101
+ },
102
+ localConfig,
103
+ defaultConfig
104
+ );
105
+ const expanded = expandPreReleaseShorthand(merged);
106
+
107
+ return {
108
+ options: expanded,
109
+ localConfig
110
+ };
111
+ }
112
+
113
+ function expandPreReleaseShorthand(options) {
114
+ const { increment, preRelease, preReleaseId, snapshot, preReleaseBase } = options;
115
+ const isPreRelease = Boolean(preRelease) || Boolean(snapshot);
116
+ const inc = snapshot ? 'prerelease' : increment;
117
+ const preId = typeof preRelease === 'string' ? preRelease : typeof snapshot === 'string' ? snapshot : preReleaseId;
118
+ options.version = {
119
+ increment: inc,
120
+ isPreRelease,
121
+ preReleaseId: preId,
122
+ preReleaseBase
123
+ };
124
+ if (typeof snapshot === 'string' && options.git) {
125
+ // Pre set and hard code some options
126
+ options.git.tagMatch = `0.0.0-${snapshot}.[0-9]*`;
127
+ options.git.getLatestTagFromAllRefs = true;
128
+ options.git.requireBranch = '!main';
129
+ options.git.requireUpstream = false;
130
+ options.npm.ignoreVersion = true;
131
+ }
132
+ return options;
133
+ }
134
+
135
+ async function loadLocalConfig(constructorConfig) {
136
+ const file = resolveFile();
137
+ const dir = resolveDir();
138
+ const extend = resolveExtend();
139
+ const defaultConfig = resolveDefaultConfig();
140
+
141
+ if (file === false) return {};
142
+
143
+ const resolvedConfig = await loadC12({
144
+ name: 'release-it',
145
+ configFile: file,
146
+ packageJson: true,
147
+ rcFile: false,
148
+ envName: false,
149
+ cwd: dir,
150
+ extend,
151
+ defaultConfig
152
+ }).catch(() => {
153
+ throw new Error(`Invalid configuration file at ${file}`);
154
+ });
155
+
156
+ if (Object.keys(resolvedConfig.config).length === 0) {
157
+ throw new Error(`no such file ${resolvedConfig.configFile}`);
158
+ }
159
+
160
+ debug('Loaded local config', resolvedConfig.config);
161
+ return isObjectStrict(resolvedConfig.config) ? resolvedConfig.config : {};
162
+
163
+ function resolveFile() {
164
+ if (constructorConfig.config === false) return false;
165
+
166
+ if (constructorConfig.config === true) return '.release-it';
167
+
168
+ return constructorConfig.config ?? '.release-it';
169
+ }
170
+
171
+ function resolveDir() {
172
+ return constructorConfig.configDir ?? process.cwd();
173
+ }
174
+
175
+ function resolveExtend() {
176
+ return constructorConfig.extends === false ? false : undefined;
177
+ }
178
+
179
+ function resolveDefaultConfig() {
180
+ return {
181
+ extends: constructorConfig.extends === false ? undefined : constructorConfig.extends
182
+ };
183
+ }
184
+ }
175
185
 
176
186
  export default Config;
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getPlugins } from './plugin/factory.js';
2
2
  import Logger from './log.js';
3
- import Config, { getRemoteConfiguration } from './config.js';
3
+ import Config from './config.js';
4
4
  import Shell from './shell.js';
5
5
  import Prompt from './prompt.js';
6
6
  import Spinner from './spinner.js';
@@ -12,15 +12,7 @@ const runTasks = async (opts, di) => {
12
12
  try {
13
13
  Object.assign(container, di);
14
14
  container.config = container.config || new Config(opts);
15
-
16
- if (typeof container.config.options?.extends === 'string') {
17
- /**
18
- * If the configuration has an 'extends' property, fetch the remote configuration
19
- * and merge it into the local configuration options.
20
- */
21
- const remoteConfiguration = await getRemoteConfiguration(container.config.options.extends);
22
- container.config.options = container.config.mergeRemoteOptions(remoteConfiguration);
23
- }
15
+ await container.config.init();
24
16
 
25
17
  const { config } = container;
26
18
  const { isCI, isVerbose, verbosityLevel, isDryRun, isChangelog, isReleaseVersion } = config;
@@ -1,5 +1,5 @@
1
1
  import { EOL } from 'node:os';
2
- import { execa } from 'execa';
2
+ import { x } from 'tinyexec';
3
3
  import matcher from 'wildcard-match';
4
4
  import { format, e, fixArgs, once, castArray } from '../../util.js';
5
5
  import GitBase from '../GitBase.js';
@@ -12,7 +12,7 @@ const options = { write: false };
12
12
  const docs = 'https://git.io/release-it-git';
13
13
 
14
14
  const isGitRepo = () =>
15
- execa('git', ['rev-parse', '--git-dir']).then(
15
+ x('git', ['rev-parse', '--git-dir'], { throwOnError: true }).then(
16
16
  () => true,
17
17
  () => false
18
18
  );
@@ -131,7 +131,7 @@ class Git extends GitBase {
131
131
  async getCommitsSinceLatestTag(commitsPath = '') {
132
132
  const latestTagName = await this.getLatestTagName();
133
133
  const ref = latestTagName ? `${latestTagName}..HEAD` : 'HEAD';
134
- return this.exec(`git rev-list ${ref} --count ${commitsPath}`, { options }).then(Number);
134
+ return this.exec(`git rev-list ${ref} --count ${commitsPath ? `-- ${commitsPath}` : ''}`, { options }).then(Number);
135
135
  }
136
136
 
137
137
  async getUpstreamArgs(pushRepo) {
@@ -2,7 +2,7 @@ import { createReadStream, statSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import open from 'open';
4
4
  import { Octokit } from '@octokit/rest';
5
- import { globby } from 'globby';
5
+ import { glob } from 'tinyglobby';
6
6
  import mime from 'mime-types';
7
7
  import retry from 'async-retry';
8
8
  import newGithubReleaseUrl from 'new-github-release-url';
@@ -336,7 +336,7 @@ class GitHub extends Release {
336
336
  return true;
337
337
  }
338
338
 
339
- return globby(patterns).then(files => {
339
+ return glob(patterns).then(files => {
340
340
  if (!files.length) {
341
341
  this.log.warn(`octokit repos.uploadReleaseAssets: did not find "${assets}" relative to ${process.cwd()}`);
342
342
  }
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs'; // import fs here so it can be stubbed in tests
3
3
  import { readFile } from 'node:fs/promises';
4
- import { globby } from 'globby';
4
+ import { glob } from 'tinyglobby';
5
5
  import { Agent } from 'undici';
6
6
  import Release from '../GitRelease.js';
7
7
  import { format, e, castArray } from '../../util.js';
@@ -323,7 +323,7 @@ class GitLab extends Release {
323
323
  return noop;
324
324
  }
325
325
 
326
- return globby(patterns).then(files => {
326
+ return glob(patterns).then(files => {
327
327
  if (!files.length) {
328
328
  this.log.warn(`gitlab releases#uploadAssets: could not find "${assets}" relative to ${process.cwd()}`);
329
329
  }
package/lib/shell.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import util from 'node:util';
2
2
  import childProcess from 'node:child_process';
3
- import { execa } from 'execa';
3
+ import { x } from 'tinyexec';
4
4
  import { format } from './util.js';
5
5
 
6
6
  const debug = util.debug('release-it:shell');
@@ -72,17 +72,17 @@ class Shell {
72
72
  const [program, ...programArgs] = command;
73
73
 
74
74
  try {
75
- const { stdout: out, stderr } = await execa(program, programArgs);
75
+ const { stdout: out, stderr } = await x(program, programArgs, { throwOnError: true });
76
76
  const stdout = out === '""' ? '' : out;
77
77
  this.log.verbose(stdout, { isExternal });
78
78
  debug({ command, options, stdout, stderr });
79
- return Promise.resolve(stdout || stderr);
79
+ return Promise.resolve((stdout || stderr).trim());
80
80
  } catch (err) {
81
- if (err.stdout) {
82
- this.log.log(`\n${err.stdout}`);
81
+ if (err.output.stdout) {
82
+ this.log.log(`\n${err.output.stdout}`);
83
83
  }
84
84
  debug(err);
85
- return Promise.reject(new Error(err.stderr || err.message));
85
+ return Promise.reject(new Error(err.output.stderr || err.output.stdout || err.message));
86
86
  }
87
87
  }
88
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "19.0.0-next.3",
3
+ "version": "19.0.0-next.5",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
5
5
  "keywords": [
6
6
  "build",
@@ -78,18 +78,15 @@
78
78
  },
79
79
  "license": "MIT",
80
80
  "dependencies": {
81
- "@iarna/toml": "2.2.5",
82
81
  "@nodeutils/defaults-deep": "1.1.0",
83
82
  "@octokit/rest": "21.1.1",
84
- "@phun-ky/typeof": "1.2.3",
83
+ "@phun-ky/typeof": "1.2.4",
85
84
  "async-retry": "1.3.3",
85
+ "c12": "2.0.4",
86
86
  "ci-info": "^4.2.0",
87
- "cosmiconfig": "9.0.0",
88
87
  "eta": "3.5.0",
89
- "execa": "9.5.2",
90
88
  "git-url-parse": "16.0.1",
91
- "globby": "14.1.0",
92
- "inquirer": "12.5.0",
89
+ "inquirer": "12.5.2",
93
90
  "issue-parser": "7.0.1",
94
91
  "lodash.get": "4.4.2",
95
92
  "lodash.merge": "4.6.2",
@@ -100,29 +97,31 @@
100
97
  "os-name": "6.0.0",
101
98
  "proxy-agent": "6.5.0",
102
99
  "semver": "7.7.1",
100
+ "tinyexec": "1.0.1",
101
+ "tinyglobby": "0.2.12",
103
102
  "undici": "6.21.2",
104
- "update-notifier": "7.3.1",
105
103
  "url-join": "5.0.0",
106
104
  "wildcard-match": "5.1.4",
107
105
  "yargs-parser": "21.1.1"
108
106
  },
109
107
  "devDependencies": {
110
- "@eslint/compat": "1.2.7",
108
+ "@eslint/compat": "1.2.8",
111
109
  "@eslint/eslintrc": "3.3.1",
112
- "@eslint/js": "9.23.0",
110
+ "@eslint/js": "9.24.0",
113
111
  "@octokit/request-error": "6.1.7",
114
112
  "@types/node": "20.17.28",
115
- "eslint": "9.23.0",
116
- "eslint-plugin-import-x": "4.9.4",
113
+ "eslint": "9.24.0",
114
+ "eslint-plugin-import-x": "4.10.2",
117
115
  "globals": "16.0.0",
118
116
  "installed-check": "9.3.0",
119
- "knip": "5.46.3",
120
- "mentoss": "0.9.0",
117
+ "knip": "5.48.0",
118
+ "mentoss": "0.9.2",
121
119
  "mock-stdio": "1.0.3",
122
120
  "prettier": "3.5.3",
123
121
  "remark-cli": "12.0.1",
124
122
  "remark-preset-webpro": "1.1.1",
125
- "typescript": "5.8.2"
123
+ "tar": "6.2.1",
124
+ "typescript": "5.8.3"
126
125
  },
127
126
  "overrides": {
128
127
  "pac-resolver": "7.0.1",
package/schema/git.json CHANGED
@@ -38,8 +38,8 @@
38
38
  "default": ""
39
39
  },
40
40
  "addUntrackedFiles": {
41
- "type": "string",
42
- "default": ""
41
+ "type": "boolean",
42
+ "default": false
43
43
  },
44
44
  "commit": {
45
45
  "type": "boolean",
@@ -53,6 +53,10 @@
53
53
  },
54
54
  "default": []
55
55
  },
56
+ "commitMessage": {
57
+ "type": "string",
58
+ "default": null
59
+ },
56
60
  "tag": {
57
61
  "type": "boolean",
58
62
  "default": true
package/test/args.js ADDED
@@ -0,0 +1,27 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { parseCliArguments } from '../lib/args.js';
4
+
5
+ test('should parse boolean arguments', () => {
6
+ const args = [
7
+ '--dry-run=false',
8
+ '--ci',
9
+ '--github=false',
10
+ '--no-npm',
11
+ '--git.addUntrackedFiles=true',
12
+ '--git.commit=false',
13
+ '--no-git.tag',
14
+ '--git.commitMessage=test'
15
+ ];
16
+
17
+ const result = parseCliArguments(args);
18
+
19
+ assert.equal(result['dry-run'], false);
20
+ assert.equal(result.ci, true);
21
+ assert.equal(result.github, false);
22
+ assert.equal(result.npm, false);
23
+ assert.equal(result.git.addUntrackedFiles, true);
24
+ assert.equal(result.git.commit, false);
25
+ assert.equal(result.git.tag, false);
26
+ assert.equal(result.git.commitMessage, 'test');
27
+ });