From 66062eb542ded987b8e48ccf8ba7201846498c61 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 14 Apr 2026 14:01:48 -0700 Subject: [PATCH] feat!: make author prompt even when already specified BREAKING CHANGE: The author prompt will now be shown even if an author is already specified in the existing package.json. If the user leaves the prompt blank, the author field will be omitted from the resulting package.json instead of being set to an empty string. --- lib/default-input.js | 32 ++++++++++++----- lib/init-package-json.js | 5 +++ test/author.js | 76 ++++++++++++++++++++++++++++++++++++++++ test/dependencies.js | 1 - test/license.js | 1 - test/name-spaces.js | 2 -- test/name-uppercase.js | 1 - test/repository.js | 1 - test/scope-in-config.js | 1 - test/yes-defaults.js | 2 +- 10 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 test/author.js diff --git a/lib/default-input.js b/lib/default-input.js index 7ce2ccb4..a20217b0 100644 --- a/lib/default-input.js +++ b/lib/default-input.js @@ -204,15 +204,31 @@ if (!package.keywords) { }) } -if (!package.author) { +let author +if (package.author) { + if (typeof package.author === 'string') { + author = package.author + } else { + const authorName = package.author.name + const authorEmail = package.author.email || package.author.mail + const authorUrl = package.author.url || package.author.web + author = `${authorName}${authorEmail ? ` <${authorEmail}>` : ''}${authorUrl ? ` (${authorUrl})` : ''}` + } +} else { const authorName = getConfig('author.name') - exports.author = authorName - ? { - name: authorName, - email: getConfig('author.email'), - url: getConfig('author.url'), - } - : yes ? '' : prompt('author') + if (authorName) { + const authorEmail = getConfig('author.email') + const authorUrl = getConfig('author.url') + author = `${authorName}${authorEmail ? ` <${authorEmail}>` : ''}${authorUrl ? ` (${authorUrl})` : ''}` + } +} + +if (yes) { + if (author) { + exports.author = author + } +} else { + exports.author = prompt('author', author || undefined) } const configLicense = getConfig('license') diff --git a/lib/init-package-json.js b/lib/init-package-json.js index a560ae30..257bd5de 100644 --- a/lib/init-package-json.js +++ b/lib/init-package-json.js @@ -105,6 +105,11 @@ async function init (dir, delete pkg.content.license } + // if no author was explicitly provided, don't include one + if (!pzData.author) { + delete pkg.content.author + } + // readJson filters out empty descriptions, but init-package-json // traditionally leaves them alone if (!pkg.content.description) { diff --git a/test/author.js b/test/author.js new file mode 100644 index 00000000..84b76e6e --- /dev/null +++ b/test/author.js @@ -0,0 +1,76 @@ +const t = require('tap') +const { setup, child, isChild } = require('./fixtures/setup') + +if (isChild()) { + return child() +} + +t.test('author from config used as default', async (t) => { + const { data } = await setup(t, __filename, { + inputs: { + name: 'the-name', + author: [ + [/author: \(npmbot \(https:\/\/npmjs\.com\)\) $/, ''], + ], + }, + config: { + 'init-author-name': 'npmbot', + 'init-author-email': 'npmbot@npmjs.com', + 'init-author-url': 'https://npmjs.com', + }, + }) + + t.equal(data.author, 'npmbot (https://npmjs.com)', 'uses config author as default') +}) + +t.test('author from config in yes mode', async (t) => { + const { data } = await setup(t, __filename, { + config: { + yes: 'yes', + 'init-author-name': 'npmbot', + 'init-author-email': 'npmbot@npmjs.com', + 'init-author-url': 'https://npmjs.com', + }, + }) + + t.equal(data.author, 'npmbot (https://npmjs.com)', 'uses config author in yes mode') +}) + +t.test('author omitted when left blank', async (t) => { + const { data } = await setup(t, __filename, { + inputs: { + name: 'the-name', + author: [ + [/author: $/, ''], + ], + }, + }) + + t.equal(data.author, undefined, 'author is omitted when left blank') +}) + +t.test('author preserved from existing package.json', async (t) => { + const { data } = await setup(t, __filename, { + testdir: { + 'package.json': JSON.stringify({ + name: 'existing-package', + version: '1.0.0', + author: 'Existing Author ', + }), + }, + config: { yes: 'yes' }, + }) + + t.equal(data.author, 'Existing Author ', 'preserves existing author') +}) + +t.test('author name only from config', async (t) => { + const { data } = await setup(t, __filename, { + config: { + yes: 'yes', + 'init-author-name': 'npmbot', + }, + }) + + t.equal(data.author, 'npmbot', 'uses author name only when no email/url configured') +}) diff --git a/test/dependencies.js b/test/dependencies.js index 803a9eae..72c80598 100644 --- a/test/dependencies.js +++ b/test/dependencies.js @@ -28,7 +28,6 @@ t.test('existing dependencies', async (t) => { version: '1.0.0', type: 'commonjs', description: '', - author: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, main: 'index.js', keywords: [], diff --git a/test/license.js b/test/license.js index 8abaa9d1..15c8404c 100644 --- a/test/license.js +++ b/test/license.js @@ -22,7 +22,6 @@ t.test('license', async (t) => { description: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, license: 'Apache-2.0', - author: '', main: 'index.js', } t.has(data, wanted) diff --git a/test/name-spaces.js b/test/name-spaces.js index c5f6cff0..75ca3636 100644 --- a/test/name-spaces.js +++ b/test/name-spaces.js @@ -20,7 +20,6 @@ t.test('single space', async t => { version: '1.0.0', description: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, - author: '', main: 'index.js', } t.has(data, wanted) @@ -41,7 +40,6 @@ t.test('multiple spaces', async t => { version: '1.0.0', description: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, - author: '', main: 'index.js', } t.has(data, wanted) diff --git a/test/name-uppercase.js b/test/name-uppercase.js index 170474a4..dc4f0759 100644 --- a/test/name-uppercase.js +++ b/test/name-uppercase.js @@ -20,7 +20,6 @@ t.test('uppercase', async (t) => { version: '1.0.0', description: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, - author: '', main: 'index.js', } t.has(data, EXPECT) diff --git a/test/repository.js b/test/repository.js index f87e4103..cc744bfd 100644 --- a/test/repository.js +++ b/test/repository.js @@ -15,7 +15,6 @@ t.test('license', async (t) => { version: '1.0.0', description: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, - author: '', repository: { type: 'git', url: 'git+https://github.com/npm/cli.git', diff --git a/test/scope-in-config.js b/test/scope-in-config.js index cf244dad..df1e3d57 100644 --- a/test/scope-in-config.js +++ b/test/scope-in-config.js @@ -10,7 +10,6 @@ t.test('--yes with scope', async (t) => { name: '@scoped/tap-testdir-scope-in-config---yes-with-scope', version: '1.0.0', description: '', - author: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, main: 'index.js', keywords: [], diff --git a/test/yes-defaults.js b/test/yes-defaults.js index add71773..b220b8b9 100644 --- a/test/yes-defaults.js +++ b/test/yes-defaults.js @@ -10,7 +10,6 @@ t.test('--yes defaults', async (t) => { name: 'tap-testdir-yes-defaults---yes-defaults', version: '1.0.0', description: '', - author: '', scripts: { test: 'echo "Error: no test specified" && exit 1' }, main: 'index.js', keywords: [], @@ -21,5 +20,6 @@ t.test('--yes defaults', async (t) => { }) t.has(data, EXPECT, 'used the default data') + t.equal(data.author, undefined, 'author is omitted by default') t.equal(data.license, undefined, 'license is omitted by default') })