release-it 19.0.0-next.4 → 19.0.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/README.md CHANGED
@@ -44,7 +44,7 @@ npm install -D release-it
44
44
  "release": "release-it"
45
45
  },
46
46
  "devDependencies": {
47
- "release-it": "^18.0.0"
47
+ "release-it": "^19.0.0"
48
48
  }
49
49
  }
50
50
  ```
@@ -99,7 +99,7 @@ Here's a quick example `.release-it.json`:
99
99
 
100
100
  ```json
101
101
  {
102
- "$schema": "https://unpkg.com/release-it@18/schema/release-it.json",
102
+ "$schema": "https://unpkg.com/release-it@19/schema/release-it.json",
103
103
  "git": {
104
104
  "commitMessage": "chore: release v${version}"
105
105
  },
@@ -346,10 +346,11 @@ release-it programmatically][54] for example code.
346
346
 
347
347
  ## Node.js version support
348
348
 
349
- The latest major version is v18, supporting Node.js 20 and up:
349
+ The latest major version is v19, supporting Node.js 20 and up:
350
350
 
351
351
  | release-it | Node.js |
352
352
  | :--------: | :-----: |
353
+ | v19 | v20 |
353
354
  | v18 | v20 |
354
355
  | v17 | v18 |
355
356
  | v16 | v16 |
package/bin/release-it.js CHANGED
@@ -1,15 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import updater from 'update-notifier';
4
3
  import release from '../lib/cli.js';
5
- import { readJSON } from '../lib/util.js';
6
4
  import { parseCliArguments } from '../lib/args.js';
7
5
 
8
- const pkg = readJSON(new URL('../package.json', import.meta.url));
9
-
10
6
  const options = parseCliArguments([].slice.call(process.argv, 2));
11
7
 
12
- updater({ pkg: pkg }).notify();
13
8
  release(options).then(
14
9
  () => process.exit(0),
15
10
  ({ code }) => process.exit(Number.isInteger(code) ? code : 1)
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
  );
@@ -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,6 +1,7 @@
1
1
  // Totally much borrowed from https://github.com/semantic-release/github/blob/master/lib/success.js
2
2
  import issueParser from 'issue-parser';
3
3
 
4
+ /** @internal */
4
5
  export const getSearchQueries = (base, commits, separator = '+') => {
5
6
  const encodedSeparator = encodeURIComponent(separator);
6
7
 
@@ -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/lib/util.js CHANGED
@@ -17,7 +17,9 @@ const eta = new Eta({
17
17
  rmWhitespace: false,
18
18
  autoTrim: false
19
19
  });
20
+
20
21
  const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
22
+
21
23
  const before = (n, func) => {
22
24
  var result;
23
25
  if (typeof func != 'function') {
@@ -43,6 +45,7 @@ const tryStatFile = filePath => {
43
45
  }
44
46
  };
45
47
 
48
+ /** @internal */
46
49
  export const execOpts = {
47
50
  stdio: process.env.NODE_DEBUG && process.env.NODE_DEBUG.indexOf('release-it') === 0 ? 'pipe' : []
48
51
  };
@@ -135,6 +138,7 @@ export const e = (message, docs, fail = true) => {
135
138
  return error;
136
139
  };
137
140
 
141
+ /** @internal */
138
142
  export const touch = (path, callback) => {
139
143
  const stat = tryStatFile(path);
140
144
  if (stat && stat.isDirectory()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "19.0.0-next.4",
3
+ "version": "19.0.0",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
5
5
  "keywords": [
6
6
  "build",
@@ -78,30 +78,28 @@
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.4",
83
+ "@phun-ky/typeof": "1.2.5",
85
84
  "async-retry": "1.3.3",
85
+ "c12": "3.0.3",
86
86
  "ci-info": "^4.2.0",
87
- "cosmiconfig": "9.0.0",
88
87
  "eta": "3.5.0",
89
- "execa": "9.5.2",
90
- "git-url-parse": "16.0.1",
91
- "globby": "14.1.0",
88
+ "git-url-parse": "16.1.0",
92
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",
96
93
  "mime-types": "3.0.1",
97
94
  "new-github-release-url": "2.0.0",
98
- "open": "10.1.0",
95
+ "open": "10.1.1",
99
96
  "ora": "8.2.0",
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"
@@ -110,18 +108,19 @@
110
108
  "@eslint/compat": "1.2.8",
111
109
  "@eslint/eslintrc": "3.3.1",
112
110
  "@eslint/js": "9.24.0",
113
- "@octokit/request-error": "6.1.7",
114
- "@types/node": "20.17.28",
111
+ "@octokit/request-error": "6.1.8",
112
+ "@types/node": "20.17.30",
115
113
  "eslint": "9.24.0",
116
- "eslint-plugin-import-x": "4.10.2",
114
+ "eslint-plugin-import-x": "4.10.5",
117
115
  "globals": "16.0.0",
118
116
  "installed-check": "9.3.0",
119
- "knip": "5.48.0",
120
- "mentoss": "0.9.0",
117
+ "knip": "5.50.5",
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",
123
+ "tar": "7.4.3",
125
124
  "typescript": "5.8.3"
126
125
  },
127
126
  "overrides": {
package/test/args.js CHANGED
@@ -2,7 +2,7 @@ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { parseCliArguments } from '../lib/args.js';
4
4
 
5
- test("should parse boolean arguments", () => {
5
+ test('should parse boolean arguments', () => {
6
6
  const args = [
7
7
  '--dry-run=false',
8
8
  '--ci',
@@ -11,8 +11,8 @@ test("should parse boolean arguments", () => {
11
11
  '--git.addUntrackedFiles=true',
12
12
  '--git.commit=false',
13
13
  '--no-git.tag',
14
- '--git.commitMessage=test',
15
- ]
14
+ '--git.commitMessage=test'
15
+ ];
16
16
 
17
17
  const result = parseCliArguments(args);
18
18