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

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
@@ -400,7 +400,7 @@ Are you using release-it at work? Please consider [sponsoring me][14]!
400
400
  [29]: ./docs/git.md
401
401
  [30]: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases
402
402
  [31]: ./docs/github-releases.md
403
- [32]: https://docs.gitlab.com/ce/user/project/releases/
403
+ [32]: https://docs.gitlab.com/api/releases/
404
404
  [33]: https://gitlab.com/profile/personal_access_tokens
405
405
  [34]: ./docs/environment-variables.md
406
406
  [35]: ./docs/gitlab-releases.md
package/lib/cli.js CHANGED
@@ -1,11 +1,8 @@
1
1
  import { readJSON } from './util.js';
2
- import Log from './log.js';
3
2
  import runTasks from './index.js';
4
3
 
5
4
  const pkg = readJSON(new URL('../package.json', import.meta.url));
6
5
 
7
- const log = new Log();
8
-
9
6
  const helpText = `Release It! v${pkg.version}
10
7
 
11
8
  Usage: release-it <increment> [options]
@@ -27,10 +24,10 @@ const helpText = `Release It! v${pkg.version}
27
24
  For more details, please see https://github.com/release-it/release-it`;
28
25
 
29
26
  /** @internal */
30
- export const version = () => log.log(`v${pkg.version}`);
27
+ export const version = () => console.log(`v${pkg.version}`);
31
28
 
32
29
  /** @internal */
33
- export const help = () => log.log(helpText);
30
+ export const help = () => console.log(helpText);
34
31
 
35
32
  export default async options => {
36
33
  if (options.version) {
package/lib/config.js CHANGED
@@ -6,7 +6,7 @@ import defaultsDeep from '@nodeutils/defaults-deep';
6
6
  import { isObjectStrict } from '@phun-ky/typeof';
7
7
  import merge from 'lodash.merge';
8
8
  import get from 'lodash.get';
9
- import { readJSON, getSystemInfo } from './util.js';
9
+ import { e, 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));
@@ -38,10 +38,36 @@ const getLocalConfig = ({ file, dir = process.cwd() }) => {
38
38
  throw new Error(`Invalid configuration file at ${result.filepath}`);
39
39
  }
40
40
  debug({ cosmiconfig: result });
41
-
42
41
  return result && isObjectStrict(result.config) ? result.config : localConfig;
43
42
  };
44
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
+
45
71
  class Config {
46
72
  constructor(config = {}) {
47
73
  this.constructorConfig = config;
@@ -86,6 +112,10 @@ class Config {
86
112
  this.defaultConfig
87
113
  );
88
114
  }
115
+
116
+ mergeRemoteOptions(remoteConfiguration) {
117
+ return merge({}, this.options, remoteConfiguration);
118
+ }
89
119
  getContext(path) {
90
120
  const context = merge({}, this.options, this.contextOptions);
91
121
  return path ? get(context, path) : context;
@@ -141,4 +171,6 @@ class Config {
141
171
  }
142
172
  }
143
173
 
174
+ export { getRemoteConfiguration };
175
+
144
176
  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 from './config.js';
3
+ import Config, { getRemoteConfiguration } from './config.js';
4
4
  import Shell from './shell.js';
5
5
  import Prompt from './prompt.js';
6
6
  import Spinner from './spinner.js';
@@ -13,6 +13,15 @@ const runTasks = async (opts, di) => {
13
13
  Object.assign(container, di);
14
14
  container.config = container.config || new Config(opts);
15
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
+ }
24
+
16
25
  const { config } = container;
17
26
  const { isCI, isVerbose, verbosityLevel, isDryRun, isChangelog, isReleaseVersion } = config;
18
27
 
@@ -62,8 +71,13 @@ const runTasks = async (opts, di) => {
62
71
  const changelog = await reduceUntil(plugins, plugin => plugin.getChangelog(latestVersion));
63
72
 
64
73
  if (isChangelog) {
65
- console.log(changelog);
66
- process.exit(0);
74
+ if (changelog) {
75
+ console.log(changelog);
76
+ process.exit(0);
77
+ } else {
78
+ log.warn('No changelog found');
79
+ process.exit(1);
80
+ }
67
81
  }
68
82
 
69
83
  const incrementBase = { latestVersion, increment, isPreRelease, preReleaseId, preReleaseBase };
@@ -95,13 +109,14 @@ const runTasks = async (opts, di) => {
95
109
  version = version || (await reduceUntil(plugins, plugin => plugin.getIncrementedVersion(incrementBase)));
96
110
  }
97
111
 
98
- if (!version) {
99
- log.obtrusive(`No new version to release`);
100
- }
101
-
102
112
  if (isReleaseVersion) {
103
- console.log(version);
104
- process.exit(0);
113
+ if (version) {
114
+ console.log(version);
115
+ process.exit(0);
116
+ } else {
117
+ log.warn(`No new version to release`);
118
+ process.exit(1);
119
+ }
105
120
  }
106
121
 
107
122
  if (version) {
@@ -125,6 +140,8 @@ const runTasks = async (opts, di) => {
125
140
  await runLifeCycleHook(plugin, hook);
126
141
  }
127
142
  }
143
+ } else {
144
+ log.obtrusive(`No new version to release`);
128
145
  }
129
146
 
130
147
  log.log(`🏁 Done (in ${Math.floor(process.uptime())}s.)`);
package/lib/log.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EOL } from 'node:os';
2
- import chalk from 'chalk';
2
+ import { styleText } from 'node:util';
3
3
  import { isObjectLoose } from '@phun-ky/typeof';
4
4
  import { upperFirst } from './util.js';
5
5
 
@@ -20,21 +20,21 @@ class Logger {
20
20
  }
21
21
 
22
22
  error(...args) {
23
- console.error(chalk.red('ERROR'), ...args);
23
+ console.error([styleText('red', 'ERROR'), ...args].join(' '));
24
24
  }
25
25
 
26
26
  info(...args) {
27
- this.log(chalk.grey(...args));
27
+ console.error(styleText('grey', args.join(' ')));
28
28
  }
29
29
 
30
30
  warn(...args) {
31
- this.log(chalk.yellow('WARNING'), ...args);
31
+ console.error([styleText('yellow', 'WARNING'), ...args].join(' '));
32
32
  }
33
33
 
34
34
  verbose(...args) {
35
35
  const { isExternal } = isObjectLoose(args.at(-1)) ? args.at(-1) : {};
36
36
  if (this.shouldLog(isExternal)) {
37
- this.log(...args.filter(str => typeof str === 'string'));
37
+ console.error(...args.filter(str => typeof str === 'string'));
38
38
  }
39
39
  }
40
40
 
@@ -46,7 +46,7 @@ class Logger {
46
46
  .map(cmd => (typeof cmd === 'string' ? cmd : Array.isArray(cmd) ? cmd.join(' ') : ''))
47
47
  .join(' ');
48
48
  const message = [prefix, command, isCached ? '[cached]' : ''].join(' ').trim();
49
- this.log(message);
49
+ console.error(message);
50
50
  }
51
51
  }
52
52
 
@@ -58,7 +58,7 @@ class Logger {
58
58
 
59
59
  preview({ title, text }) {
60
60
  if (text) {
61
- const header = chalk.bold(upperFirst(title));
61
+ const header = styleText('bold', upperFirst(title));
62
62
  const body = text.replace(new RegExp(EOL + EOL, 'g'), EOL);
63
63
  this.obtrusive(`${header}:${EOL}${body}`);
64
64
  } else {
@@ -12,10 +12,6 @@ import Release from '../GitRelease.js';
12
12
  import prompts from './prompts.js';
13
13
  import { getCommitsFromChangelog, getResolvedIssuesFromChangelog, searchQueries } from './util.js';
14
14
 
15
- /**
16
- * @typedef {import('@octokit/rest').RestEndpointMethodTypes['repos']['createRelease']['parameters']} CreateReleaseOptions
17
- */
18
-
19
15
  /**
20
16
  * @typedef {import('@octokit/rest').RestEndpointMethodTypes['repos']['createRelease']['parameters']} CreateReleaseOptions
21
17
  */
@@ -411,7 +407,7 @@ class GitHub extends Release {
411
407
 
412
408
  async commentOnResolvedItems() {
413
409
  const { isDryRun } = this.config;
414
- const { owner, project: repo } = this.getContext('repo');
410
+ const { host, owner, project: repo } = this.getContext('repo');
415
411
  const changelog = await this.getChangelog();
416
412
  const { comments } = this.options;
417
413
  const { submit, issue, pr } = comments ?? {};
@@ -423,7 +419,7 @@ class GitHub extends Release {
423
419
  const searchResults = await Promise.all(searchQueries(this.client, owner, repo, shas));
424
420
  const mergedPullRequests = searchResults.flatMap(items => items.map(item => ({ type: 'pr', number: item.number })));
425
421
 
426
- const hostURL = 'https://' + (this.options.host || repo.host);
422
+ const hostURL = 'https://' + (this.options.host || host);
427
423
  const resolvedIssues = getResolvedIssuesFromChangelog(hostURL, owner, repo, changelog);
428
424
 
429
425
  for (const item of [...resolvedIssues, ...mergedPullRequests]) {
@@ -18,7 +18,7 @@ export const getSearchQueries = (base, commits, separator = '+') => {
18
18
 
19
19
  export const searchQueries = (client, owner, repo, shas) =>
20
20
  getSearchQueries(`repo:${owner}/${repo}+type:pr+is:merged`, shas).map(
21
- async q => (await client.search.issuesAndPullRequests({ q })).data.items
21
+ async q => (await client.search.issuesAndPullRequests({ q, advanced_search: true })).data.items
22
22
  );
23
23
 
24
24
  export const getCommitsFromChangelog = changelog => {
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
- import fs, { promises } from 'node:fs'; // import fs here so it can be stubbed in tests
2
+ import fs from 'node:fs'; // import fs here so it can be stubbed in tests
3
+ import { readFile } from 'node:fs/promises';
3
4
  import { globby } from 'globby';
4
5
  import { Agent } from 'undici';
5
6
  import Release from '../GitRelease.js';
@@ -15,7 +16,10 @@ class GitLab extends Release {
15
16
  super(...args);
16
17
  this.registerPrompts(prompts);
17
18
  this.assets = [];
18
- const { certificateAuthorityFile, secure } = this.options;
19
+ const { secure } = this.options;
20
+ const certificateAuthorityFileRef = this.options.certificateAuthorityFileRef || 'CI_SERVER_TLS_CA_FILE';
21
+ const certificateAuthorityFile =
22
+ this.options.certificateAuthorityFile || process.env[certificateAuthorityFileRef] || null;
19
23
  this.certificateAuthorityOption = {};
20
24
 
21
25
  const needsCustomAgent = Boolean(secure === false || certificateAuthorityFile);
@@ -261,14 +265,17 @@ class GitLab extends Release {
261
265
  const name = path.basename(filePath);
262
266
  const { useIdsForUrls, useGenericPackageRepositoryForAssets, genericPackageRepositoryName } = this.options;
263
267
  const { id, origin, repo, version, baseUrl } = this.getContext();
268
+
264
269
  const endpoint = useGenericPackageRepositoryForAssets
265
270
  ? `projects/${id}/packages/generic/${genericPackageRepositoryName}/${version}/${name}`
266
271
  : `projects/${id}/uploads`;
272
+
267
273
  if (useGenericPackageRepositoryForAssets) {
268
274
  const options = {
269
275
  method: 'PUT',
270
- body: await promises.readFile(filePath)
276
+ body: await readFile(filePath)
271
277
  };
278
+
272
279
  try {
273
280
  const body = await this.request(endpoint, options);
274
281
  if (!(body.message && body.message == '201 Created')) {
@@ -285,7 +292,7 @@ class GitLab extends Release {
285
292
  }
286
293
  } else {
287
294
  const body = new FormData();
288
- const rawData = await promises.readFile(filePath);
295
+ const rawData = await readFile(filePath);
289
296
  body.set('file', new Blob([rawData]), name);
290
297
  const options = { body };
291
298
 
@@ -256,6 +256,10 @@ class npm extends Plugin {
256
256
  })
257
257
  .catch(err => {
258
258
  this.debug(err);
259
+ if (this.config.isDryRun && /publish over the previously published version/.test(err)) {
260
+ return Promise.resolve();
261
+ }
262
+
259
263
  if (/one-time pass/.test(err)) {
260
264
  if (otp != null) {
261
265
  this.log.warn('The provided OTP is incorrect or has expired.');
@@ -1,9 +1,7 @@
1
+ import { styleText } from 'node:util';
1
2
  import semver from 'semver';
2
- import chalk from 'chalk';
3
3
  import Plugin from '../Plugin.js';
4
4
 
5
- const { green, red, redBright } = chalk;
6
-
7
5
  const RELEASE_TYPES = ['patch', 'minor', 'major'];
8
6
  const PRERELEASE_TYPES = ['prepatch', 'preminor', 'premajor'];
9
7
  const CONTINUATION_TYPES = ['prerelease', 'pre'];
@@ -30,7 +28,11 @@ const getIncrementChoices = context => {
30
28
  };
31
29
 
32
30
  const versionTransformer = context => input =>
33
- semver.valid(input) ? (semver.gt(input, context.latestVersion) ? green(input) : red(input)) : redBright(input);
31
+ semver.valid(input)
32
+ ? semver.gt(input, context.latestVersion)
33
+ ? styleText('green', input)
34
+ : styleText('red', input)
35
+ : styleText(['red', 'bold'], input);
34
36
 
35
37
  const prompts = {
36
38
  incrementList: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "19.0.0-next.1",
3
+ "version": "19.0.0-next.3",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
5
5
  "keywords": [
6
6
  "build",
@@ -48,9 +48,9 @@
48
48
  "type": "module",
49
49
  "exports": {
50
50
  ".": {
51
+ "types": "./types/index.d.ts",
51
52
  "import": "./lib/index.js",
52
- "require": "./lib/index.js",
53
- "types": "./types/index.d.ts"
53
+ "require": "./lib/index.js"
54
54
  },
55
55
  "./package.json": "./package.json",
56
56
  "./test/util/index.js": "./test/util/index.js"
@@ -69,7 +69,7 @@
69
69
  "lint": "eslint lib test",
70
70
  "format": "prettier --write eslint.config.mjs \"{lib,test}/**/*.js\"",
71
71
  "docs": "remark README.md 'docs/**/*.md' '.github/*.md' -o",
72
- "test": "ava --no-worker-threads && installed-check --ignore ava --ignore nock",
72
+ "test": "node --env-file=.env.test --test && installed-check",
73
73
  "release": "./bin/release-it.js"
74
74
  },
75
75
  "author": {
@@ -83,25 +83,24 @@
83
83
  "@octokit/rest": "21.1.1",
84
84
  "@phun-ky/typeof": "1.2.3",
85
85
  "async-retry": "1.3.3",
86
- "chalk": "5.4.1",
87
86
  "ci-info": "^4.2.0",
88
87
  "cosmiconfig": "9.0.0",
89
88
  "eta": "3.5.0",
90
89
  "execa": "9.5.2",
91
90
  "git-url-parse": "16.0.1",
92
91
  "globby": "14.1.0",
93
- "inquirer": "12.4.3",
92
+ "inquirer": "12.5.0",
94
93
  "issue-parser": "7.0.1",
95
94
  "lodash.get": "4.4.2",
96
95
  "lodash.merge": "4.6.2",
97
- "mime-types": "2.1.35",
96
+ "mime-types": "3.0.1",
98
97
  "new-github-release-url": "2.0.0",
99
98
  "open": "10.1.0",
100
99
  "ora": "8.2.0",
101
100
  "os-name": "6.0.0",
102
101
  "proxy-agent": "6.5.0",
103
102
  "semver": "7.7.1",
104
- "undici": "6.21.1",
103
+ "undici": "6.21.2",
105
104
  "update-notifier": "7.3.1",
106
105
  "url-join": "5.0.0",
107
106
  "wildcard-match": "5.1.4",
@@ -109,28 +108,20 @@
109
108
  },
110
109
  "devDependencies": {
111
110
  "@eslint/compat": "1.2.7",
112
- "@eslint/eslintrc": "3.3.0",
113
- "@eslint/js": "9.22.0",
111
+ "@eslint/eslintrc": "3.3.1",
112
+ "@eslint/js": "9.23.0",
114
113
  "@octokit/request-error": "6.1.7",
115
- "@types/node": "20.17.17",
116
- "ava": "6.2.0",
117
- "eslint": "9.22.0",
118
- "eslint-config-prettier": "10.1.1",
119
- "eslint-plugin-ava": "15.0.1",
120
- "eslint-plugin-import-x": "4.6.1",
121
- "eslint-plugin-prettier": "5.2.3",
122
- "fs-monkey": "1.0.6",
114
+ "@types/node": "20.17.28",
115
+ "eslint": "9.23.0",
116
+ "eslint-plugin-import-x": "4.9.4",
123
117
  "globals": "16.0.0",
124
118
  "installed-check": "9.3.0",
125
- "knip": "5.45.0",
126
- "memfs": "4.17.0",
119
+ "knip": "5.46.3",
120
+ "mentoss": "0.9.0",
127
121
  "mock-stdio": "1.0.3",
128
- "nock": "14.0.1",
129
122
  "prettier": "3.5.3",
130
123
  "remark-cli": "12.0.1",
131
124
  "remark-preset-webpro": "1.1.1",
132
- "sinon": "19.0.2",
133
- "strip-ansi": "7.1.0",
134
125
  "typescript": "5.8.2"
135
126
  },
136
127
  "overrides": {
@@ -46,6 +46,10 @@
46
46
  "certificateAuthorityFile": {
47
47
  "default": null
48
48
  },
49
+ "certificateAuthorityFileRef": {
50
+ "type": "string",
51
+ "default": "CI_SERVER_TLS_CA_FILE"
52
+ },
49
53
  "secure": {
50
54
  "default": null
51
55
  },
@@ -9,6 +9,10 @@
9
9
  "type": "string",
10
10
  "description": "The JSON schema version used to validate this configuration file"
11
11
  },
12
+ "extends": {
13
+ "type": "string",
14
+ "description": "URL that specifies a configuration to extend"
15
+ },
12
16
  "hooks": {
13
17
  "type": "object",
14
18
  "additionalProperties": true,
package/test/cli.js CHANGED
@@ -1,20 +1,21 @@
1
- import test from 'ava';
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
2
3
  import mockStdIo from 'mock-stdio';
3
4
  import { version, help } from '../lib/cli.js';
4
5
  import { readJSON } from '../lib/util.js';
5
6
 
6
7
  const pkg = readJSON(new URL('../package.json', import.meta.url));
7
8
 
8
- test('should print version', t => {
9
+ test('should print version', () => {
9
10
  mockStdIo.start();
10
11
  version();
11
12
  const { stdout } = mockStdIo.end();
12
- t.is(stdout, `v${pkg.version}\n`);
13
+ assert.equal(stdout, `v${pkg.version}\n`);
13
14
  });
14
15
 
15
- test('should print help', t => {
16
+ test('should print help', () => {
16
17
  mockStdIo.start();
17
18
  help();
18
19
  const { stdout } = mockStdIo.end();
19
- t.regex(stdout, RegExp(`Release It!.+${pkg.version}`));
20
+ assert.match(stdout, new RegExp(`Release It!.+${pkg.version}`));
20
21
  });