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

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/lib/config.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import util from 'node:util';
2
2
  import { cosmiconfigSync } from 'cosmiconfig';
3
3
  import parseToml from '@iarna/toml/parse-string.js';
4
- import _ from 'lodash';
5
4
  import { isCI } from 'ci-info';
5
+ import defaultsDeep from '@nodeutils/defaults-deep';
6
+ import { isObjectStrict } from '@phun-ky/typeof';
7
+ import merge from 'lodash.merge';
8
+ import get from 'lodash.get';
6
9
  import { readJSON, getSystemInfo } from './util.js';
7
10
 
8
11
  const debug = util.debug('release-it:config');
@@ -35,7 +38,8 @@ const getLocalConfig = ({ file, dir = process.cwd() }) => {
35
38
  throw new Error(`Invalid configuration file at ${result.filepath}`);
36
39
  }
37
40
  debug({ cosmiconfig: result });
38
- return result && _.isPlainObject(result.config) ? result.config : localConfig;
41
+
42
+ return result && isObjectStrict(result.config) ? result.config : localConfig;
39
43
  };
40
44
 
41
45
  class Config {
@@ -72,7 +76,7 @@ class Config {
72
76
  }
73
77
 
74
78
  mergeOptions() {
75
- return _.defaultsDeep(
79
+ return defaultsDeep(
76
80
  {},
77
81
  this.constructorConfig,
78
82
  {
@@ -82,15 +86,14 @@ class Config {
82
86
  this.defaultConfig
83
87
  );
84
88
  }
85
-
86
89
  getContext(path) {
87
- const context = _.merge({}, this.options, this.contextOptions);
88
- return path ? _.get(context, path) : context;
90
+ const context = merge({}, this.options, this.contextOptions);
91
+ return path ? get(context, path) : context;
89
92
  }
90
93
 
91
94
  setContext(options) {
92
95
  debug(options);
93
- _.merge(this.contextOptions, options);
96
+ merge(this.contextOptions, options);
94
97
  }
95
98
 
96
99
  setCI(value = true) {
package/lib/index.js CHANGED
@@ -1,11 +1,10 @@
1
- import _ from 'lodash';
2
1
  import { getPlugins } from './plugin/factory.js';
3
2
  import Logger from './log.js';
4
3
  import Config from './config.js';
5
4
  import Shell from './shell.js';
6
5
  import Prompt from './prompt.js';
7
6
  import Spinner from './spinner.js';
8
- import { reduceUntil, parseVersion } from './util.js';
7
+ import { reduceUntil, parseVersion, castArray } from './util.js';
9
8
 
10
9
  const runTasks = async (opts, di) => {
11
10
  let container = {};
@@ -33,20 +32,20 @@ const runTasks = async (opts, di) => {
33
32
  if (!scripts || !scripts.length) return;
34
33
  const context = config.getContext();
35
34
  const external = true;
36
- for (const script of _.castArray(scripts)) {
35
+ for (const script of castArray(scripts)) {
37
36
  const task = () => shell.exec(script, { external }, context);
38
37
  await spinner.show({ task, label: script, context, external });
39
38
  }
40
39
  };
41
40
 
42
41
  const runLifeCycleHook = async (plugin, name, ...args) => {
43
- if (plugin === _.first(plugins)) await runHook('before', name);
42
+ if (plugin === plugins.at(0)) await runHook('before', name);
44
43
  await runHook('before', plugin.namespace, name);
45
44
  const willHookRun = (await plugin[name](...args)) !== false;
46
45
  if (willHookRun) {
47
46
  await runHook('after', plugin.namespace, name);
48
47
  }
49
- if (plugin === _.last(plugins)) await runHook('after', name);
48
+ if (plugin === plugins.at(-1)) await runHook('after', name);
50
49
  };
51
50
 
52
51
  const [internal, external] = await getPlugins(config, container);
package/lib/log.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { EOL } from 'node:os';
2
2
  import chalk from 'chalk';
3
- import _ from 'lodash';
4
-
5
- const { isObject, last, filter, isString, lowerCase, upperFirst, isArray } = _;
3
+ import { isObjectLoose } from '@phun-ky/typeof';
4
+ import { upperFirst } from './util.js';
6
5
 
7
6
  class Logger {
8
7
  constructor({ isCI = true, isVerbose = false, verbosityLevel = 0, isDryRun = false } = {}) {
@@ -33,17 +32,19 @@ class Logger {
33
32
  }
34
33
 
35
34
  verbose(...args) {
36
- const { isExternal } = isObject(last(args)) ? last(args) : {};
35
+ const { isExternal } = isObjectLoose(args.at(-1)) ? args.at(-1) : {};
37
36
  if (this.shouldLog(isExternal)) {
38
- this.log(...filter(args, isString));
37
+ this.log(...args.filter(str => typeof str === 'string'));
39
38
  }
40
39
  }
41
40
 
42
41
  exec(...args) {
43
- const { isDryRun: isExecutedInDryRun, isExternal, isCached } = isObject(last(args)) ? last(args) : {};
42
+ const { isDryRun: isExecutedInDryRun, isExternal, isCached } = isObjectLoose(args.at(-1)) ? args.at(-1) : {};
44
43
  if (this.shouldLog(isExternal) || this.isDryRun) {
45
44
  const prefix = isExecutedInDryRun == null ? '$' : '!';
46
- const command = args.map(cmd => (isString(cmd) ? cmd : isArray(cmd) ? cmd.join(' ') : '')).join(' ');
45
+ const command = args
46
+ .map(cmd => (typeof cmd === 'string' ? cmd : Array.isArray(cmd) ? cmd.join(' ') : ''))
47
+ .join(' ');
47
48
  const message = [prefix, command, isCached ? '[cached]' : ''].join(' ').trim();
48
49
  this.log(message);
49
50
  }
@@ -61,7 +62,7 @@ class Logger {
61
62
  const body = text.replace(new RegExp(EOL + EOL, 'g'), EOL);
62
63
  this.obtrusive(`${header}:${EOL}${body}`);
63
64
  } else {
64
- this.obtrusive(`Empty ${lowerCase(title)}`);
65
+ this.obtrusive(`Empty ${title.toLowerCase()}`);
65
66
  }
66
67
  }
67
68
  }
@@ -22,7 +22,8 @@ class GitBase extends Plugin {
22
22
  }
23
23
 
24
24
  getName() {
25
- return this.getContext('repo.project');
25
+ const repo = this.getContext('repo');
26
+ return repo.project;
26
27
  }
27
28
 
28
29
  getLatestVersion() {
@@ -1,5 +1,4 @@
1
- import _ from 'lodash';
2
- import { readJSON } from '../util.js';
1
+ import { pick, readJSON } from '../util.js';
3
2
  import GitBase from './GitBase.js';
4
3
 
5
4
  const defaultConfig = readJSON(new URL('../../config/release-it.json', import.meta.url));
@@ -12,7 +11,7 @@ class GitRelease extends GitBase {
12
11
  getInitialOptions(options) {
13
12
  const baseOptions = super.getInitialOptions(...arguments);
14
13
  const git = options.git || defaultConfig.git;
15
- const gitOptions = _.pick(git, [
14
+ const gitOptions = pick(git, [
16
15
  'tagExclude',
17
16
  'tagName',
18
17
  'tagMatch',
@@ -21,12 +20,13 @@ class GitRelease extends GitBase {
21
20
  'changelog',
22
21
  'commit'
23
22
  ]);
24
- return _.defaults(baseOptions, gitOptions);
23
+
24
+ return Object.assign({}, gitOptions, baseOptions);
25
25
  }
26
26
 
27
27
  get token() {
28
28
  const { tokenRef } = this.options;
29
- return _.get(process.env, tokenRef, null);
29
+ return process.env[tokenRef] || null;
30
30
  }
31
31
 
32
32
  async beforeRelease() {
@@ -1,5 +1,6 @@
1
1
  import { debug } from 'node:util';
2
- import _ from 'lodash';
2
+ import merge from 'lodash.merge';
3
+ import get from 'lodash.get';
3
4
 
4
5
  class Plugin {
5
6
  static isEnabled() {
@@ -40,12 +41,13 @@ class Plugin {
40
41
  afterRelease() {}
41
42
 
42
43
  getContext(path) {
43
- const context = _.merge({}, this.options, this.context);
44
- return path ? _.get(context, path) : context;
44
+ const context = merge({}, this.options, this.context);
45
+
46
+ return path ? get(context, path) : context;
45
47
  }
46
48
 
47
49
  setContext(context) {
48
- _.merge(this.context, context);
50
+ merge(this.context, context);
49
51
  }
50
52
 
51
53
  exec(command, { options, context = {} } = {}) {
@@ -2,7 +2,7 @@ import url from 'node:url';
2
2
  import path from 'node:path';
3
3
  import util from 'node:util';
4
4
  import { createRequire } from 'node:module';
5
- import _ from 'lodash';
5
+ import { castArray } from '../util.js';
6
6
  import Version from './version/Version.js';
7
7
  import Git from './git/Git.js';
8
8
  import GitLab from './gitlab/GitLab.js';
@@ -54,23 +54,23 @@ export const getPluginName = pluginName => {
54
54
  export let getPlugins = async (config, container) => {
55
55
  const context = config.getContext();
56
56
  const disabledPlugins = [];
57
-
58
- const enabledExternalPlugins = await _.reduce(
59
- context.plugins,
60
- async (result, pluginConfig, pluginName) => {
57
+ const enabledExternalPlugins = [];
58
+ if (context.plugins) {
59
+ for (const [pluginName, pluginConfig] of Object.entries(context.plugins)) {
61
60
  const [name, Plugin] = await load(pluginName);
62
61
  const [namespace, options] = pluginConfig.length === 2 ? pluginConfig : [name, pluginConfig];
62
+
63
63
  config.setContext({ [namespace]: options });
64
+
64
65
  if (await Plugin.isEnabled(options)) {
65
66
  const instance = new Plugin({ namespace, options: config.getContext(), container });
66
67
  debug({ namespace, options: instance.options });
67
- (await result).push(instance);
68
- disabledPlugins.push(..._.intersection(pluginNames, _.castArray(Plugin.disablePlugin(options))));
68
+ enabledExternalPlugins.push(instance);
69
+
70
+ disabledPlugins.push(...pluginNames.filter(p => castArray(Plugin.disablePlugin(options)).includes(p)));
69
71
  }
70
- return result;
71
- },
72
- []
73
- );
72
+ }
73
+ }
74
74
 
75
75
  const enabledPlugins = await pluginNames.reduce(async (result, plugin) => {
76
76
  if (plugin in plugins && !disabledPlugins.includes(plugin)) {
@@ -1,8 +1,7 @@
1
1
  import { EOL } from 'node:os';
2
- import _ from 'lodash';
3
2
  import { execa } from 'execa';
4
3
  import matcher from 'wildcard-match';
5
- import { format, e, fixArgs } from '../../util.js';
4
+ import { format, e, fixArgs, once, castArray } from '../../util.js';
6
5
  import GitBase from '../GitBase.js';
7
6
  import prompts from './prompts.js';
8
7
 
@@ -64,7 +63,7 @@ class Git extends GitBase {
64
63
  }
65
64
 
66
65
  enableRollback() {
67
- this.rollbackOnce = _.once(this.rollback.bind(this));
66
+ this.rollbackOnce = once(this.rollback.bind(this));
68
67
  process.on('SIGINT', this.rollbackOnce);
69
68
  process.on('exit', this.rollbackOnce);
70
69
  }
@@ -96,7 +95,7 @@ class Git extends GitBase {
96
95
 
97
96
  async isRequiredBranch() {
98
97
  const branch = await this.getBranchName();
99
- const requiredBranches = _.castArray(this.options.requireBranch);
98
+ const requiredBranches = castArray(this.options.requireBranch);
100
99
  const [branches, negated] = requiredBranches.reduce(
101
100
  ([p, n], b) => (b.startsWith('!') ? [p, [...n, b.slice(1)]] : [[...p, b], n]),
102
101
  [[], []]
@@ -151,7 +150,7 @@ class Git extends GitBase {
151
150
 
152
151
  stage(file) {
153
152
  if (!file || !file.length) return noop;
154
- const files = _.castArray(file);
153
+ const files = castArray(file);
155
154
  return this.exec(['git', 'add', ...files]).catch(err => {
156
155
  this.log.warn(`Could not stage ${files}`);
157
156
  this.debug(err);
@@ -164,7 +163,7 @@ class Git extends GitBase {
164
163
  }
165
164
 
166
165
  reset(file) {
167
- const files = _.castArray(file);
166
+ const files = castArray(file);
168
167
  return this.exec(['git', 'checkout', 'HEAD', '--', ...files]).catch(err => {
169
168
  this.log.warn(`Could not reset ${files}`);
170
169
  this.debug(err);
@@ -4,11 +4,10 @@ import open from 'open';
4
4
  import { Octokit } from '@octokit/rest';
5
5
  import { globby } from 'globby';
6
6
  import mime from 'mime-types';
7
- import _ from 'lodash';
8
7
  import retry from 'async-retry';
9
8
  import newGithubReleaseUrl from 'new-github-release-url';
10
9
  import { ProxyAgent } from 'proxy-agent';
11
- import { format, parseVersion, readJSON, e } from '../../util.js';
10
+ import { format, parseVersion, readJSON, e, castArray } from '../../util.js';
12
11
  import Release from '../GitRelease.js';
13
12
  import prompts from './prompts.js';
14
13
  import { getCommitsFromChangelog, getResolvedIssuesFromChangelog, searchQueries } from './util.js';
@@ -34,7 +33,7 @@ const parseErrormsg = err => {
34
33
  if (err instanceof Error) {
35
34
  const { status, message } = err;
36
35
  const headers = err.response ? err.response.headers : {};
37
- msg = `${_.get(headers, 'status', status)} (${message})`;
36
+ msg = `${headers?.status || status} (${message})`;
38
37
  }
39
38
  return msg;
40
39
  };
@@ -166,7 +165,7 @@ class GitHub extends Release {
166
165
  const githubError = new Error(message);
167
166
  this.log.verbose(err.errors);
168
167
  this.debug(err);
169
- if (!_.includes(RETRY_CODES, err.status)) {
168
+ if (!RETRY_CODES.includes(err.status)) {
170
169
  return bail(githubError);
171
170
  }
172
171
  throw githubError;
@@ -175,7 +174,8 @@ class GitHub extends Release {
175
174
  get client() {
176
175
  if (this._client) return this._client;
177
176
  const { proxy, timeout } = this.options;
178
- const host = this.options.host || this.getContext('repo.host');
177
+ const repo = this.getContext('repo');
178
+ const host = this.options.host || repo.host;
179
179
  const isGitHub = host === 'github.com';
180
180
  const baseUrl = `https://${isGitHub ? 'api.github.com' : host}${isGitHub ? '' : '/api/v3'}`;
181
181
  const options = {
@@ -226,6 +226,7 @@ class GitHub extends Release {
226
226
  const { isPreRelease } = parseVersion(version);
227
227
  const name = format(releaseName, this.config.getContext());
228
228
  const releaseNotesObject = this.options.releaseNotes;
229
+
229
230
  const body = autoGenerate
230
231
  ? isUpdate
231
232
  ? null
@@ -331,7 +332,7 @@ class GitHub extends Release {
331
332
  const context = this.config.getContext();
332
333
  const { isDryRun } = this.config;
333
334
 
334
- const patterns = _.castArray(assets).map(pattern => format(pattern, context));
335
+ const patterns = castArray(assets).map(pattern => format(pattern, context));
335
336
 
336
337
  this.log.exec('octokit repos.uploadReleaseAssets', patterns, { isDryRun });
337
338
 
@@ -356,9 +357,9 @@ class GitHub extends Release {
356
357
  }
357
358
 
358
359
  async generateWebUrl() {
359
- const host = this.options.host || this.getContext('repo.host');
360
+ const repo = this.getContext('repo');
361
+ const host = this.options.host || repo.host;
360
362
  const isGitHub = host === 'github.com';
361
-
362
363
  const options = await this.getOctokitReleaseOptions();
363
364
  const url = newGithubReleaseUrl({
364
365
  user: options.owner,
@@ -422,13 +423,13 @@ class GitHub extends Release {
422
423
  const searchResults = await Promise.all(searchQueries(this.client, owner, repo, shas));
423
424
  const mergedPullRequests = searchResults.flatMap(items => items.map(item => ({ type: 'pr', number: item.number })));
424
425
 
425
- const host = 'https://' + (this.options.host || this.getContext('repo.host'));
426
- const resolvedIssues = getResolvedIssuesFromChangelog(host, owner, repo, changelog);
426
+ const hostURL = 'https://' + (this.options.host || repo.host);
427
+ const resolvedIssues = getResolvedIssuesFromChangelog(hostURL, owner, repo, changelog);
427
428
 
428
429
  for (const item of [...resolvedIssues, ...mergedPullRequests]) {
429
430
  const { type, number } = item;
430
431
  const comment = format(format(type === 'pr' ? pr : issue, context), context);
431
- const url = `${host}/${owner}/${repo}/${type === 'pr' ? 'pull' : 'issues'}/${number}`;
432
+ const url = `${hostURL}/${owner}/${repo}/${type === 'pr' ? 'pull' : 'issues'}/${number}`;
432
433
 
433
434
  if (isDryRun) {
434
435
  this.log.exec(`octokit issues.createComment (${url})`, { isDryRun });
@@ -1,10 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import fs, { promises } from 'node:fs'; // import fs here so it can be stubbed in tests
3
3
  import { globby } from 'globby';
4
- import _ from 'lodash';
5
4
  import { Agent } from 'undici';
6
5
  import Release from '../GitRelease.js';
7
- import { format, e } from '../../util.js';
6
+ import { format, e, castArray } from '../../util.js';
8
7
  import prompts from './prompts.js';
9
8
 
10
9
  const docs = 'https://git.io/release-it-gitlab';
@@ -309,7 +308,7 @@ class GitLab extends Release {
309
308
  const { isDryRun } = this.config;
310
309
  const context = this.config.getContext();
311
310
 
312
- const patterns = _.castArray(assets).map(pattern => format(pattern, context));
311
+ const patterns = castArray(assets).map(pattern => format(pattern, context));
313
312
 
314
313
  this.log.exec('gitlab releases#uploadAssets', patterns, { isDryRun });
315
314
 
package/lib/util.js CHANGED
@@ -1,25 +1,57 @@
1
1
  import fs, { close, openSync, statSync, utimesSync, accessSync } from 'node:fs'; // need import fs here due to test stubbing
2
2
  import util from 'node:util';
3
3
  import { EOL } from 'node:os';
4
- import _ from 'lodash';
5
4
  import gitUrlParse from 'git-url-parse';
6
5
  import semver from 'semver';
7
6
  import osName from 'os-name';
7
+ import { Eta } from 'eta';
8
8
  import Log from './log.js';
9
9
 
10
+ const debug = util.debug('release-it:shell');
11
+
12
+ const eta = new Eta({
13
+ autoEscape: false,
14
+ useWith: true,
15
+ tags: ['${', '}'],
16
+ parse: { interpolate: '' },
17
+ rmWhitespace: false,
18
+ autoTrim: false
19
+ });
20
+ const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
21
+ const before = (n, func) => {
22
+ var result;
23
+ if (typeof func != 'function') {
24
+ throw new TypeError('Missing argument for `func`');
25
+ }
26
+ n = parseInt(n);
27
+ return function () {
28
+ if (--n > 0) {
29
+ result = func.apply(this, arguments);
30
+ }
31
+ if (n <= 1) {
32
+ func = undefined;
33
+ }
34
+ return result;
35
+ };
36
+ };
37
+ const tryStatFile = filePath => {
38
+ try {
39
+ return statSync(filePath);
40
+ } catch (e) {
41
+ debug(e);
42
+ return null;
43
+ }
44
+ };
45
+
10
46
  export const execOpts = {
11
47
  stdio: process.env.NODE_DEBUG && process.env.NODE_DEBUG.indexOf('release-it') === 0 ? 'pipe' : []
12
48
  };
13
49
 
14
- const debug = util.debug('release-it:shell');
15
-
16
- const readJSON = file => JSON.parse(fs.readFileSync(file, 'utf8'));
50
+ export const readJSON = file => JSON.parse(fs.readFileSync(file, 'utf8'));
17
51
 
18
52
  const pkg = readJSON(new URL('../package.json', import.meta.url));
19
53
 
20
- const log = new Log();
21
-
22
- const getSystemInfo = () => {
54
+ export const getSystemInfo = () => {
23
55
  return {
24
56
  'release-it': pkg.version,
25
57
  node: process.version,
@@ -27,9 +59,11 @@ const getSystemInfo = () => {
27
59
  };
28
60
  };
29
61
 
30
- const format = (template = '', context = {}) => {
62
+ export const format = (template = '', context = {}) => {
63
+ if (!context || context === null || !template || template === null || template.indexOf('${') === -1) return template;
64
+ const log = new Log();
31
65
  try {
32
- return _.template(template)(context);
66
+ return eta.renderString(template, context);
33
67
  } catch (error) {
34
68
  log.error(`Unable to render template with context:\n${template}\n${JSON.stringify(context)}`);
35
69
  log.error(error);
@@ -37,21 +71,19 @@ const format = (template = '', context = {}) => {
37
71
  }
38
72
  };
39
73
 
40
- const truncateLines = (input, maxLines = 10, surplusText = null) => {
74
+ export const truncateLines = (input, maxLines = 10, surplusText = null) => {
41
75
  const lines = input.split(EOL);
42
76
  const surplus = lines.length - maxLines;
43
77
  const output = lines.slice(0, maxLines).join(EOL);
44
78
  return surplus > 0 ? (surplusText ? `${output}${surplusText}` : `${output}${EOL}...and ${surplus} more`) : output;
45
79
  };
46
80
 
47
- const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
48
-
49
- const rejectAfter = (ms, error) =>
81
+ export const rejectAfter = (ms, error) =>
50
82
  wait(ms).then(() => {
51
83
  throw error;
52
84
  });
53
85
 
54
- const parseGitUrl = remoteUrl => {
86
+ export const parseGitUrl = remoteUrl => {
55
87
  if (!remoteUrl) return { host: null, owner: null, project: null, protocol: null, remote: null, repository: null };
56
88
  const normalizedUrl = (remoteUrl || '')
57
89
  .replace(/^[A-Z]:\\\\/, 'file://') // Assume file protocol for Windows drive letters
@@ -59,12 +91,12 @@ const parseGitUrl = remoteUrl => {
59
91
  .replace(/\\+/g, '/'); // Replace forward with backslashes
60
92
  const parsedUrl = gitUrlParse(normalizedUrl);
61
93
  const { resource: host, name: project, protocol, href: remote } = parsedUrl;
62
- const owner = protocol === 'file' ? _.last(parsedUrl.owner.split('/')) : parsedUrl.owner; // Fix owner for file protocol
94
+ const owner = protocol === 'file' ? parsedUrl.owner.split('/').at(-1) : parsedUrl.owner; // Fix owner for file protocol
63
95
  const repository = `${owner}/${project}`;
64
96
  return { host, owner, project, protocol, remote, repository };
65
97
  };
66
98
 
67
- const reduceUntil = async (collection, fn) => {
99
+ export const reduceUntil = async (collection, fn) => {
68
100
  let result;
69
101
  for (const item of collection) {
70
102
  if (result) break;
@@ -73,7 +105,7 @@ const reduceUntil = async (collection, fn) => {
73
105
  return result;
74
106
  };
75
107
 
76
- const hasAccess = path => {
108
+ export const hasAccess = path => {
77
109
  try {
78
110
  accessSync(path);
79
111
  return true;
@@ -82,7 +114,7 @@ const hasAccess = path => {
82
114
  }
83
115
  };
84
116
 
85
- const parseVersion = raw => {
117
+ export const parseVersion = raw => {
86
118
  if (raw == null) return { version: raw, isPreRelease: false, preReleaseId: null };
87
119
  const version = semver.valid(raw) ? raw : semver.coerce(raw);
88
120
  if (!version) return { version: raw, isPreRelease: false, preReleaseId: null };
@@ -96,14 +128,14 @@ const parseVersion = raw => {
96
128
  };
97
129
  };
98
130
 
99
- const e = (message, docs, fail = true) => {
131
+ export const e = (message, docs, fail = true) => {
100
132
  const error = new Error(docs ? `${message}${EOL}Documentation: ${docs}${EOL}` : message);
101
133
  error.code = fail ? 1 : 0;
102
134
  error.cause = fail ? 'ERROR' : 'INFO';
103
135
  return error;
104
136
  };
105
137
 
106
- const touch = (path, callback) => {
138
+ export const touch = (path, callback) => {
107
139
  const stat = tryStatFile(path);
108
140
  if (stat && stat.isDirectory()) {
109
141
  // don't error just exit
@@ -121,28 +153,25 @@ const touch = (path, callback) => {
121
153
  }
122
154
  };
123
155
 
124
- const tryStatFile = filePath => {
125
- try {
126
- return statSync(filePath);
127
- } catch (e) {
128
- debug(e);
129
- return null;
130
- }
156
+ export const fixArgs = args => (args ? (typeof args === 'string' ? args.split(' ') : args) : []);
157
+
158
+ export const upperFirst = string => {
159
+ return string ? string.charAt(0).toUpperCase() + string.slice(1) : '';
160
+ };
161
+
162
+ export const castArray = arr => {
163
+ return Array.isArray(arr) ? arr : [arr];
164
+ };
165
+
166
+ export const once = fn => {
167
+ return before(2, fn);
131
168
  };
132
169
 
133
- const fixArgs = args => (args ? (typeof args === 'string' ? args.split(' ') : args) : []);
134
-
135
- export {
136
- getSystemInfo,
137
- format,
138
- truncateLines,
139
- rejectAfter,
140
- reduceUntil,
141
- parseGitUrl,
142
- hasAccess,
143
- parseVersion,
144
- readJSON,
145
- fixArgs,
146
- e,
147
- touch
170
+ export const pick = (object, keys) => {
171
+ return keys.reduce((obj, key) => {
172
+ if (object && Object.prototype.hasOwnProperty.call(object, key)) {
173
+ obj[key] = object[key];
174
+ }
175
+ return obj;
176
+ }, {});
148
177
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "19.0.0-next.0",
3
+ "version": "19.0.0-next.1",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
5
5
  "keywords": [
6
6
  "build",
@@ -79,17 +79,21 @@
79
79
  "license": "MIT",
80
80
  "dependencies": {
81
81
  "@iarna/toml": "2.2.5",
82
- "@octokit/rest": "21.1.0",
82
+ "@nodeutils/defaults-deep": "1.1.0",
83
+ "@octokit/rest": "21.1.1",
84
+ "@phun-ky/typeof": "1.2.3",
83
85
  "async-retry": "1.3.3",
84
86
  "chalk": "5.4.1",
85
- "ci-info": "^4.1.0",
87
+ "ci-info": "^4.2.0",
86
88
  "cosmiconfig": "9.0.0",
89
+ "eta": "3.5.0",
87
90
  "execa": "9.5.2",
88
- "git-url-parse": "16.0.0",
91
+ "git-url-parse": "16.0.1",
89
92
  "globby": "14.1.0",
90
- "inquirer": "12.4.1",
93
+ "inquirer": "12.4.3",
91
94
  "issue-parser": "7.0.1",
92
- "lodash": "4.17.21",
95
+ "lodash.get": "4.4.2",
96
+ "lodash.merge": "4.6.2",
93
97
  "mime-types": "2.1.35",
94
98
  "new-github-release-url": "2.0.0",
95
99
  "open": "10.1.0",
@@ -104,30 +108,30 @@
104
108
  "yargs-parser": "21.1.1"
105
109
  },
106
110
  "devDependencies": {
107
- "@eslint/compat": "1.2.6",
108
- "@eslint/eslintrc": "3.2.0",
109
- "@eslint/js": "9.20.0",
110
- "@octokit/request-error": "6.1.6",
111
+ "@eslint/compat": "1.2.7",
112
+ "@eslint/eslintrc": "3.3.0",
113
+ "@eslint/js": "9.22.0",
114
+ "@octokit/request-error": "6.1.7",
111
115
  "@types/node": "20.17.17",
112
116
  "ava": "6.2.0",
113
- "eslint": "9.20.1",
114
- "eslint-config-prettier": "10.0.1",
117
+ "eslint": "9.22.0",
118
+ "eslint-config-prettier": "10.1.1",
115
119
  "eslint-plugin-ava": "15.0.1",
116
120
  "eslint-plugin-import-x": "4.6.1",
117
121
  "eslint-plugin-prettier": "5.2.3",
118
122
  "fs-monkey": "1.0.6",
119
- "globals": "15.14.0",
123
+ "globals": "16.0.0",
120
124
  "installed-check": "9.3.0",
121
- "knip": "5.44.0",
125
+ "knip": "5.45.0",
122
126
  "memfs": "4.17.0",
123
127
  "mock-stdio": "1.0.3",
124
128
  "nock": "14.0.1",
125
- "prettier": "3.5.0",
129
+ "prettier": "3.5.3",
126
130
  "remark-cli": "12.0.1",
127
131
  "remark-preset-webpro": "1.1.1",
128
132
  "sinon": "19.0.2",
129
133
  "strip-ansi": "7.1.0",
130
- "typescript": "5.7.3"
134
+ "typescript": "5.8.2"
131
135
  },
132
136
  "overrides": {
133
137
  "pac-resolver": "7.0.1",
@@ -2,7 +2,6 @@ import path from 'node:path';
2
2
  import { renameSync } from 'node:fs';
3
3
  import childProcess from 'node:child_process';
4
4
  import test from 'ava';
5
- import _ from 'lodash';
6
5
  import sinon from 'sinon';
7
6
  import Log from '../lib/log.js';
8
7
  import Spinner from '../lib/spinner.js';
@@ -105,7 +104,7 @@ test.serial('should not run hooks for disabled release-cycle methods', async t =
105
104
 
106
105
  await runTasks({}, container);
107
106
 
108
- const commands = _.flatten(exec.args).filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
107
+ const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
109
108
 
110
109
  t.true(commands.includes('echo before:init'));
111
110
  t.true(commands.includes('echo after:afterRelease'));
@@ -140,7 +139,7 @@ test.serial('should not run hooks for cancelled release-cycle methods', async t
140
139
 
141
140
  await runTasks({}, container);
142
141
 
143
- const commands = _.flatten(exec.args).filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
142
+ const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
144
143
 
145
144
  t.true(commands.includes('echo before:init'));
146
145
  t.true(commands.includes('echo after:afterRelease'));
@@ -199,7 +198,7 @@ test.serial('should run "after:*:release" plugin hooks', async t => {
199
198
 
200
199
  await runTasks({}, container);
201
200
 
202
- const commands = _.flatten(exec.args).filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
201
+ const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
203
202
 
204
203
  t.true(commands.includes('echo after:git:bump'));
205
204
  t.true(commands.includes('echo after:npm:bump'));
package/test/tasks.js CHANGED
@@ -3,7 +3,6 @@ import childProcess from 'node:child_process';
3
3
  import { appendFileSync, mkdirSync, renameSync } from 'node:fs';
4
4
  import test from 'ava';
5
5
  import semver from 'semver';
6
- import _ from 'lodash';
7
6
  import sinon from 'sinon';
8
7
  import Log from '../lib/log.js';
9
8
  import Spinner from '../lib/spinner.js';
@@ -529,7 +528,7 @@ test.serial('should use custom changelog command with context', async t => {
529
528
 
530
529
  await runTasks({}, container);
531
530
 
532
- const commands = _.flatten(exec.args).filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
531
+ const commands = exec.args.flat().filter(arg => typeof arg === 'string' && arg.startsWith('echo'));
533
532
 
534
533
  t.deepEqual(commands, [
535
534
  'echo before:init',
@@ -1,4 +1,3 @@
1
- import _ from 'lodash';
2
1
  import sinon from 'sinon';
3
2
  import semver from 'semver';
4
3
  import { parseVersion } from '../../lib/util.js';
@@ -9,10 +8,8 @@ import Spinner from '../../lib/spinner.js';
9
8
  import Prompt from '../../lib/prompt.js';
10
9
 
11
10
  export let factory = (Definition, { namespace, options = {}, container = {} } = {}) => {
12
- _.defaults(options, { ci: true, verbose: false, 'dry-run': false, debug: false });
13
-
11
+ options = Object.assign({}, { ci: true, verbose: false, 'dry-run': false, debug: false }, options);
14
12
  const ns = namespace || Definition.name.toLowerCase();
15
-
16
13
  container.config = container.config || new Config(Object.assign({ config: false }, options));
17
14
  container.log = container.log || sinon.createStubInstance(Log);
18
15