diff --git a/docs/content/commands/npm-pkg.md b/docs/content/commands/npm-pkg.md index 7ff0a4d97930f..fe87411e41ee1 100644 --- a/docs/content/commands/npm-pkg.md +++ b/docs/content/commands/npm-pkg.md @@ -98,6 +98,13 @@ Returned values are always in **json** format. npm pkg set contributors[0].name='Foo' contributors[0].email='foo@bar.ca' ``` + You may also append items to the end of an array using the special + empty bracket notation: + + ```bash + npm pkg set contributors[].name='Bar' contributors[].email='bar@bar.ca' + ``` + It's also possible to parse values as json prior to saving them to your `package.json` file, for example in order to set a `"private": true` property: diff --git a/lib/utils/queryable.js b/lib/utils/queryable.js index 173877e64817c..35bf5530d618d 100644 --- a/lib/utils/queryable.js +++ b/lib/utils/queryable.js @@ -10,6 +10,7 @@ const cleanLeadingDot = str => const parseKeys = (key) => { const sqBracketItems = new Set() const parseSqBrackets = (str) => { + str = str.replace('[]', '[-0]') const index = sqBracketsMatcher(str) // once we find square brackets, we recursively parse all these @@ -124,8 +125,17 @@ const setter = ({ data, key, value, force }) => { const maybeIndex = Number(_key) if (!Number.isNaN(maybeIndex)) { _key = maybeIndex + + // creates new array in case it's missing if (!Object.keys(_data).length) _data = [] + + // in case it's using a negative index, than calculates the + // length of the array minus that negative index, it's also used + // as a handy shortcut to empty-bracket-appending syntax since + // it converts missing indexes like this: arr[] -> arr[-0] + if (_key < 0 || Object.is(_key, -0)) + _key = (_data.length) + _key } // retrieves the next data object to recursively iterate on, diff --git a/test/lib/pkg.js b/test/lib/pkg.js index 42eb7c0cc5e9c..688df6859054a 100644 --- a/test/lib/pkg.js +++ b/test/lib/pkg.js @@ -291,6 +291,38 @@ t.test('set single field', t => { }) }) +t.test('push to array syntax', t => { + const json = { + name: 'foo', + version: '1.1.1', + keywords: [ + 'foo', + ], + } + npm.localPrefix = t.testdir({ + 'package.json': JSON.stringify(json), + }) + + pkg.exec(['set', 'keywords[]=bar', 'keywords[]=baz'], err => { + if (err) + throw err + + t.strictSame( + readPackageJson(), + { + ...json, + keywords: [ + 'foo', + 'bar', + 'baz', + ], + }, + 'should append to arrays using empty bracket syntax' + ) + t.end() + }) +}) + t.test('set multiple fields', t => { const json = { name: 'foo', diff --git a/test/lib/utils/queryable.js b/test/lib/utils/queryable.js index 2e66eeeb9e080..176f09c5fbfb5 100644 --- a/test/lib/utils/queryable.js +++ b/test/lib/utils/queryable.js @@ -602,6 +602,61 @@ t.test('set arrays', async t => { { code: 'EOVERRIDEVALUE' }, 'should throw an override error' ) + + qqq.set('arr[]', 'c') + t.strictSame( + qqq.toJSON(), + { + arr: [ + 'a', + 'b', + 'c', + ], + }, + 'should be able to append to array using empty bracket notation' + ) + + qqq.set('arr[-2]', 'B') + t.strictSame( + qqq.toJSON(), + { + arr: [ + 'a', + 'B', + 'c', + ], + }, + 'should be able to use negative indexes' + ) + + qqq.set('arr[-1]', 'C') + t.strictSame( + qqq.toJSON(), + { + arr: [ + 'a', + 'B', + 'C', + ], + }, + 'should be able to use negative indexes' + ) + + qqq.set('arr[].foo', 'foo') + t.strictSame( + qqq.toJSON(), + { + arr: [ + 'a', + 'B', + 'C', + { + foo: 'foo', + }, + ], + }, + 'should be able to append objects to array using empty bracket notation' + ) }) t.test('delete values', async t => {