From d24fdd8cabd70034d0e531f09dd8fdd2aeaed38f Mon Sep 17 00:00:00 2001 From: Michael Garvin Date: Tue, 21 Apr 2026 14:03:34 -0700 Subject: [PATCH 1/2] chore: refactor tests --- .../test/lib/commands/pkg.js.test.cjs | 215 ++++++++ test/lib/commands/pkg.js | 480 ++++++------------ 2 files changed, 358 insertions(+), 337 deletions(-) create mode 100644 tap-snapshots/test/lib/commands/pkg.js.test.cjs diff --git a/tap-snapshots/test/lib/commands/pkg.js.test.cjs b/tap-snapshots/test/lib/commands/pkg.js.test.cjs new file mode 100644 index 0000000000000..95d9e6b86b0dd --- /dev/null +++ b/tap-snapshots/test/lib/commands/pkg.js.test.cjs @@ -0,0 +1,215 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/lib/commands/pkg.js TAP delete multiple field > should delete multiple fields from package.json 1`] = ` +Object { + "name": "foo", +} +` + +exports[`test/lib/commands/pkg.js TAP delete nested field > should delete nested fields from package.json 1`] = ` +Object { + "info": Object { + "foo": Object { + "bar": Array [ + Object {}, + ], + }, + }, + "name": "foo", + "version": "1.0.0", +} +` + +exports[`test/lib/commands/pkg.js TAP delete single field > should delete single field from package.json 1`] = ` +Object { + "name": "foo", +} +` + +exports[`test/lib/commands/pkg.js TAP fix > fixes package.json issues 1`] = ` +Object { + "name": "foo", + "version": "1.1.1", +} +` + +exports[`test/lib/commands/pkg.js TAP get array field > should print retrieved array field 1`] = ` +[ + "index.js", + "cli.js" +] +` + +exports[`test/lib/commands/pkg.js TAP get array item > should print retrieved array field 1`] = ` + +` + +exports[`test/lib/commands/pkg.js TAP get array nested items notation > should print json result containing matching results 1`] = ` +{ + "contributors[0].name": "Ruy", + "contributors[1].name": "Gar" +} +` + +exports[`test/lib/commands/pkg.js TAP get multiple arg > should print retrieved package.json fields 1`] = ` +{ + "name": "foo", + "version": "1.1.1" +} +` + +exports[`test/lib/commands/pkg.js TAP get multiple arg with empty value > should print retrieved package.json field regardless of empty value 1`] = ` +{ + "name": "foo", + "author": "" +} +` + +exports[`test/lib/commands/pkg.js TAP get multiple arg with only one arg existing > should print retrieved package.json field 1`] = ` +{ + "name": "foo" +} +` + +exports[`test/lib/commands/pkg.js TAP get nested arg > node test.js 1`] = ` +"node test.js" +` + +exports[`test/lib/commands/pkg.js TAP get no args > should print package.json content 1`] = ` +{ + "name": "foo", + "version": "1.1.1" +} +` + +exports[`test/lib/commands/pkg.js TAP get single arg > should print retrieved package.json field 1`] = ` +"1.1.1" +` + +exports[`test/lib/commands/pkg.js TAP push to array syntax > should append to arrays using empty bracket syntax 1`] = ` +Object { + "keywords": Array [ + "foo", + "bar", + "baz", + ], + "name": "foo", + "version": "1.1.1", +} +` + +exports[`test/lib/commands/pkg.js TAP set --json > should add fields to package.json 1`] = ` +Object { + "description": "awesome", + "foo": Object { + "bar": Object { + "baz": "BAZ", + }, + }, + "name": "foo", + "private": true, + "tap": Object { + "timeout": 60, + }, + "version": "1.1.1", + "workspaces": Array [ + "packages/*", + ], +} +` + +exports[`test/lib/commands/pkg.js TAP set = separate value > should add single field to package.json 1`] = ` +Object { + "name": "foo", + "tap": Object { + "test-env": Array [ + "LC_ALL=sk", + ], + }, + "version": "1.1.1", +} +` + +exports[`test/lib/commands/pkg.js TAP set multiple fields > should add single field to package.json 1`] = ` +Object { + "bin": Object { + "foo": "foo.js", + }, + "name": "foo", + "scripts": Object { + "test": "node test.js", + }, + "version": "1.1.1", +} +` + +exports[`test/lib/commands/pkg.js TAP set single field > should add single field to package.json 1`] = ` +Object { + "description": "Awesome stuff", + "name": "foo", + "version": "1.1.1", +} +` + +exports[`test/lib/commands/pkg.js TAP single workspace multiple args > should only return info for one workspace 1`] = ` +{ + "a": { + "name": "a", + "version": "1.0.0" + } +} +` + +exports[`test/lib/commands/pkg.js TAP single workspace single arg > should only return info for one workspace 1`] = ` +{ + "a": "1.0.0" +} +` + +exports[`test/lib/commands/pkg.js TAP workspaces get > should return expected result for configured workspaces 1`] = ` +{ + "a": { + "name": "a", + "version": "1.0.0" + }, + "b": { + "name": "b", + "version": "1.2.3" + } +} +` + +exports[`test/lib/commands/pkg.js TAP workspaces set > should add field to workspace a 1`] = ` +Object { + "funding": "http://example.com", + "name": "a", + "version": "1.0.0", +} +` + +exports[`test/lib/commands/pkg.js TAP workspaces set > should add field to workspace b 1`] = ` +Object { + "funding": "http://example.com", + "name": "b", + "version": "1.2.3", +} +` + +exports[`test/lib/commands/pkg.js TAP workspaces set > should delete version field from workspace a 1`] = ` +Object { + "funding": "http://example.com", + "name": "a", +} +` + +exports[`test/lib/commands/pkg.js TAP workspaces set > should delete version field from workspace b 1`] = ` +Object { + "funding": "http://example.com", + "name": "b", +} +` diff --git a/test/lib/commands/pkg.js b/test/lib/commands/pkg.js index f4d0278b04d9b..3d176617d445f 100644 --- a/test/lib/commands/pkg.js +++ b/test/lib/commands/pkg.js @@ -1,52 +1,40 @@ const { resolve } = require('node:path') const { readFileSync } = require('node:fs') const t = require('tap') -const _mockNpm = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') const { cleanCwd } = require('../../fixtures/clean-snapshot') t.cleanSnapshot = (str) => cleanCwd(str) -const mockNpm = async (t, { ...opts } = {}) => { - const res = await _mockNpm(t, { - ...opts, - command: 'pkg', - }) - - const readPackageJson = (dir = '') => - JSON.parse(readFileSync(resolve(res.prefix, dir, 'package.json'), 'utf8')) - - return { - ...res, - pkg: (...args) => res.npm.exec('pkg', args), - readPackageJson, - OUTPUT: () => res.joinedOutput(), - } -} +const readPackageJson = (prefix, dir = '') => + JSON.parse(readFileSync(resolve(prefix, dir, 'package.json'), 'utf8')) t.test('no args', async t => { - const { pkg } = await mockNpm(t) + const { npm } = await loadMockNpm(t) await t.rejects( - pkg(), + npm.exec('pkg'), { code: 'EUSAGE' }, 'should throw usage error' ) }) t.test('no global mode', async t => { - const { pkg } = await mockNpm(t, { - config: { global: true }, + const { npm } = await loadMockNpm(t, { + config: { + global: true + } }) await t.rejects( - pkg('get', 'foo'), + npm.exec('pkg'), { code: 'EPKGGLOBAL' }, 'should throw no global mode error' ) }) t.test('get no args', async t => { - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -54,21 +42,16 @@ t.test('get no args', async t => { }), }, }) + await npm.exec('pkg', ['get']) - await pkg('get') - - t.strictSame( - JSON.parse(OUTPUT()), - { - name: 'foo', - version: '1.1.1', - }, + t.matchSnapshot( + joinedOutput(), 'should print package.json content' ) }) t.test('get single arg', async t => { - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -77,17 +60,16 @@ t.test('get single arg', async t => { }, }) - await pkg('get', 'version') + await npm.exec('pkg', ['get', 'version']) - t.strictSame( - JSON.parse(OUTPUT()), - '1.1.1', + t.matchSnapshot( + joinedOutput(), 'should print retrieved package.json field' ) }) t.test('get multiple arg', async t => { - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -96,20 +78,16 @@ t.test('get multiple arg', async t => { }, }) - await pkg('get', 'name', 'version') + await npm.exec('pkg', ['get', 'name', 'version']) - t.strictSame( - JSON.parse(OUTPUT()), - { - name: 'foo', - version: '1.1.1', - }, - 'should print retrieved package.json field' + t.matchSnapshot( + joinedOutput(), + 'should print retrieved package.json fields' ) }) t.test('get multiple arg with only one arg existing', async t => { - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -117,19 +95,16 @@ t.test('get multiple arg with only one arg existing', async t => { }, }) - await pkg('get', 'name', 'version', 'dependencies') + await npm.exec('pkg', ['get', 'name', 'version', 'dependencies']) - t.strictSame( - JSON.parse(OUTPUT()), - { - name: 'foo', - }, + t.matchSnapshot( + joinedOutput(), 'should print retrieved package.json field' ) }) t.test('get multiple arg with empty value', async t => { - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -138,20 +113,16 @@ t.test('get multiple arg with empty value', async t => { }, }) - await pkg('get', 'name', 'author') + await npm.exec('pkg', ['get', 'name', 'author']) - t.strictSame( - JSON.parse(OUTPUT()), - { - name: 'foo', - author: '', - }, + t.matchSnapshot( + joinedOutput(), 'should print retrieved package.json field regardless of empty value' ) }) t.test('get nested arg', async t => { - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -163,129 +134,120 @@ t.test('get nested arg', async t => { }, }) - await pkg('get', 'scripts.test') + await npm.exec('pkg', ['get', 'scripts.test']) - t.strictSame( - JSON.parse(OUTPUT()), + t.matchSnapshot( + joinedOutput(), 'node test.js', 'should print retrieved nested field' ) }) t.test('get array field', async t => { - const files = [ - 'index.js', - 'cli.js', - ] - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', version: '1.1.1', - files, + files: [ + 'index.js', + 'cli.js', + ], }), }, }) - await pkg('get', 'files') + await npm.exec('pkg', ['get', 'files']) - t.strictSame( - JSON.parse(OUTPUT()), - files, + t.matchSnapshot( + joinedOutput(), 'should print retrieved array field' ) }) t.test('get array item', async t => { - const files = [ - 'index.js', - 'cli.js', - ] - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', version: '1.1.1', - files, + files: [, + 'index.js', + 'cli.js', + ], }), }, }) - await pkg('get', 'files[0]') + await npm.exec('pkg', ['get', 'files[0]']) - t.strictSame( - JSON.parse(OUTPUT()), - 'index.js', + t.matchSnapshot( + joinedOutput(), 'should print retrieved array field' ) }) t.test('get array nested items notation', async t => { - const contributors = [ - { - name: 'Ruy', - url: 'http://example.com/ruy', - }, - { - name: 'Gar', - url: 'http://example.com/gar', - }, - ] - const { pkg, OUTPUT } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', version: '1.1.1', - contributors, + contributors: [ + { + name: 'Ruy', + url: 'http://example.com/ruy', + }, + { + name: 'Gar', + url: 'http://example.com/gar', + }, + ], }), }, }) - await pkg('get', 'contributors.name') - t.strictSame( - JSON.parse(OUTPUT()), - { - 'contributors[0].name': 'Ruy', - 'contributors[1].name': 'Gar', - }, + await npm.exec('pkg', ['get', 'contributors.name']) + t.matchSnapshot( + joinedOutput(), 'should print json result containing matching results' ) }) t.test('set no args', async t => { - const { pkg } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo' }), }, }) await t.rejects( - pkg('set'), + npm.exec('pkg', ['set']), { code: 'EUSAGE' }, 'should throw an error if no args' ) }) t.test('set missing value', async t => { - const { pkg } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo' }), }, }) await t.rejects( - pkg('set', 'key='), + npm.exec('pkg', ['set', 'key=']), { code: 'EUSAGE' }, 'should throw an error if missing value' ) }) t.test('set missing key', async t => { - const { pkg } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo' }), }, }) await t.rejects( - pkg('set', '=value'), + npm.exec('pkg', ['set', '=value']), { code: 'EUSAGE' }, 'should throw an error if missing key' ) @@ -296,19 +258,15 @@ t.test('set single field', async t => { name: 'foo', version: '1.1.1', } - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify(json), }, }) - await pkg('set', 'description=Awesome stuff') - t.strictSame( - readPackageJson(), - { - ...json, - description: 'Awesome stuff', - }, + await npm.exec('pkg', ['set', 'description=Awesome stuff']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'should add single field to package.json' ) }) @@ -321,23 +279,15 @@ t.test('push to array syntax', async t => { 'foo', ], } - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify(json), }, }) - await pkg('set', 'keywords[]=bar', 'keywords[]=baz') - t.strictSame( - readPackageJson(), - { - ...json, - keywords: [ - 'foo', - 'bar', - 'baz', - ], - }, + await npm.exec('pkg', ['set', 'keywords[]=bar', 'keywords[]=baz']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'should append to arrays using empty bracket syntax' ) }) @@ -347,24 +297,15 @@ t.test('set multiple fields', async t => { name: 'foo', version: '1.1.1', } - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify(json), }, }) - await pkg('set', 'bin.foo=foo.js', 'scripts.test=node test.js') - t.strictSame( - readPackageJson(), - { - ...json, - bin: { - foo: 'foo.js', - }, - scripts: { - test: 'node test.js', - }, - }, + await npm.exec('pkg', ['set', 'bin.foo=foo.js', 'scripts.test=node test.js']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'should add single field to package.json' ) }) @@ -374,29 +315,21 @@ t.test('set = separate value', async t => { name: 'foo', version: '1.1.1', } - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify(json), }, }) - await pkg('set', 'tap[test-env][0]=LC_ALL=sk') - t.strictSame( - readPackageJson(), - { - ...json, - tap: { - 'test-env': [ - 'LC_ALL=sk', - ], - }, - }, + await npm.exec('pkg', ['set', 'tap[test-env][0]=LC_ALL=sk']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'should add single field to package.json' ) }) t.test('set --json', async t => { - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -406,124 +339,45 @@ t.test('set --json', async t => { config: { json: true }, }) - await pkg('set', 'private=true') - t.strictSame( - readPackageJson(), - { - name: 'foo', - version: '1.1.1', - private: true, - }, - 'should add boolean field to package.json' - ) - - await pkg('set', 'tap.timeout=60') - t.strictSame( - readPackageJson(), - { - name: 'foo', - version: '1.1.1', - private: true, - tap: { - timeout: 60, - }, - }, - 'should add number field to package.json' - ) - - await pkg('set', 'foo={ "bar": { "baz": "BAZ" } }') - t.strictSame( - readPackageJson(), - { - name: 'foo', - version: '1.1.1', - private: true, - tap: { - timeout: 60, - }, - foo: { - bar: { - baz: 'BAZ', - }, - }, - }, - 'should add object field to package.json' - ) - - await pkg('set', 'workspaces=["packages/*"]') - t.strictSame( - readPackageJson(), - { - name: 'foo', - version: '1.1.1', - private: true, - workspaces: [ - 'packages/*', - ], - tap: { - timeout: 60, - }, - foo: { - bar: { - baz: 'BAZ', - }, - }, - }, - 'should add object field to package.json' - ) - - await pkg('set', 'description="awesome"') - t.strictSame( - readPackageJson(), - { - name: 'foo', - version: '1.1.1', - description: 'awesome', - private: true, - workspaces: [ - 'packages/*', - ], - tap: { - timeout: 60, - }, - foo: { - bar: { - baz: 'BAZ', - }, - }, - }, - 'should add object field to package.json' + await npm.exec('pkg', ['set', 'private=true']) + await npm.exec('pkg', ['set', 'tap.timeout=60']) + await npm.exec('pkg', ['set', 'foo={ "bar": { "baz": "BAZ" } }']) + await npm.exec('pkg', ['set', 'workspaces=["packages/*"]']) + await npm.exec('pkg', ['set', 'description="awesome"']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should add fields to package.json' ) }) t.test('delete no args', async t => { - const { pkg } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo' }), }, }) await t.rejects( - pkg('delete'), + npm.exec('pkg', ['delete']), { code: 'EUSAGE' }, 'should throw an error if deleting no args' ) }) t.test('delete invalid key', async t => { - const { pkg } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo' }), }, }) await t.rejects( - pkg('delete', ''), + npm.exec('pkg', ['delete', '']), { code: 'EUSAGE' }, 'should throw an error if deleting invalid args' ) }) t.test('delete single field', async t => { - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -531,18 +385,15 @@ t.test('delete single field', async t => { }), }, }) - await pkg('delete', 'version') - t.strictSame( - readPackageJson(), - { - name: 'foo', - }, + await npm.exec('pkg', ['delete', 'version']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'should delete single field from package.json' ) }) t.test('delete multiple field', async t => { - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -551,18 +402,15 @@ t.test('delete multiple field', async t => { }), }, }) - await pkg('delete', 'version', 'description') - t.strictSame( - readPackageJson(), - { - name: 'foo', - }, + await npm.exec('pkg', ['delete', 'version', 'description']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'should delete multiple fields from package.json' ) }) t.test('delete nested field', async t => { - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo', @@ -579,26 +427,15 @@ t.test('delete nested field', async t => { }), }, }) - await pkg('delete', 'info.foo.bar[0].baz') - t.strictSame( - readPackageJson(), - { - name: 'foo', - version: '1.0.0', - info: { - foo: { - bar: [ - {}, - ], - }, - }, - }, + await npm.exec('pkg', ['delete', 'info.foo.bar[0].baz']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'should delete nested fields from package.json' ) }) t.test('workspaces', async t => { - const mockWorkspaces = (t) => mockNpm(t, { + const workspaceSetup = { prefixDir: { 'package.json': JSON.stringify({ name: 'root', @@ -623,76 +460,48 @@ t.test('workspaces', async t => { }, }, config: { workspaces: true }, - }) + } t.test('get', async t => { - const { pkg, OUTPUT } = await mockWorkspaces(t) - await pkg('get', 'name', 'version') - t.strictSame( - JSON.parse(OUTPUT()), - { - a: { - name: 'a', - version: '1.0.0', - }, - b: { - name: 'b', - version: '1.2.3', - }, - }, + const { npm, joinedOutput } = await loadMockNpm(t, workspaceSetup) + await npm.exec('pkg', ['get', 'name', 'version']) + t.matchSnapshot( + joinedOutput(), 'should return expected result for configured workspaces' ) }) t.test('set', async t => { - const { pkg, readPackageJson } = await mockWorkspaces(t) + const { npm, joinedOutput } = await loadMockNpm(t, workspaceSetup) - await pkg('set', 'funding=http://example.com') + await npm.exec('pkg', ['set', 'funding=http://example.com']) - t.strictSame( - readPackageJson('packages/a'), - { - name: 'a', - version: '1.0.0', - funding: 'http://example.com', - }, + t.matchSnapshot( + readPackageJson(npm.prefix, 'packages/a'), 'should add field to workspace a' ) - t.strictSame( - readPackageJson('packages/b'), - { - name: 'b', - version: '1.2.3', - funding: 'http://example.com', - }, + t.matchSnapshot( + readPackageJson(npm.prefix, 'packages/b'), 'should add field to workspace b' ) - await pkg('delete', 'version') + await npm.exec('pkg', ['delete', 'version']) - t.strictSame( - readPackageJson('packages/a'), - { - name: 'a', - funding: 'http://example.com', - }, + t.matchSnapshot( + readPackageJson(npm.prefix, 'packages/a'), 'should delete version field from workspace a' ) - t.strictSame( - readPackageJson('packages/b'), - { - name: 'b', - funding: 'http://example.com', - }, + t.matchSnapshot( + readPackageJson(npm.prefix, 'packages/b'), 'should delete version field from workspace b' ) }) }) t.test('single workspace', async t => { - const mockWorkspace = (t) => mockNpm(t, { + const workspaceSetup = { prefixDir: { 'package.json': JSON.stringify({ name: 'root', @@ -717,33 +526,31 @@ t.test('single workspace', async t => { }, }, config: { workspace: ['packages/a'] }, - }) + } t.test('multiple args', async t => { - const { pkg, OUTPUT } = await mockWorkspace(t) - await pkg('get', 'name', 'version') + const { npm, joinedOutput } = await loadMockNpm(t, workspaceSetup) + await npm.exec('pkg', ['get', 'name', 'version']) - t.strictSame( - JSON.parse(OUTPUT()), - { a: { name: 'a', version: '1.0.0' } }, + t.matchSnapshot( + joinedOutput(), 'should only return info for one workspace' ) }) t.test('single arg', async t => { - const { pkg, OUTPUT } = await mockWorkspace(t) - await pkg('get', 'version') + const { npm, joinedOutput } = await loadMockNpm(t, workspaceSetup) + await npm.exec('pkg', ['get', 'version']) - t.strictSame( - JSON.parse(OUTPUT()), - { a: '1.0.0' }, + t.matchSnapshot( + joinedOutput(), 'should only return info for one workspace' ) }) }) t.test('fix', async t => { - const { pkg, readPackageJson } = await mockNpm(t, { + const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo ', @@ -752,10 +559,9 @@ t.test('fix', async t => { }, }) - await pkg('fix') - t.strictSame( - readPackageJson(), - { name: 'foo', version: '1.1.1' }, + await npm.exec('pkg', ['fix']) + t.matchSnapshot( + readPackageJson(npm.prefix), 'fixes package.json issues' ) }) From 65bf37e6442595a3c75d7378551d59542a7c37d3 Mon Sep 17 00:00:00 2001 From: Michael Garvin Date: Wed, 22 Apr 2026 09:09:29 -0700 Subject: [PATCH 2/2] fix(pkg): output like npm view does, do not force json output BREAKING CHANGE: The `npm pkg` output is no longer forced to json. This means you can get single values without having to worry about wrapping of the values. It also outputs non-json content more similarly to `npm view`. Fixes https://github.com/npm/statusboard/issues/1080 --- lib/commands/pkg.js | 40 +- lib/commands/view.js | 1 + .../tap-snapshots/test/index.js.test.cjs | 36 +- .../test/lib/commands/pkg.js.test.cjs | 85 +- test/lib/commands/pkg.js | 758 ++++++++++-------- 5 files changed, 509 insertions(+), 411 deletions(-) diff --git a/lib/commands/pkg.js b/lib/commands/pkg.js index 3eccb24769eb6..a47c7695c3d5e 100644 --- a/lib/commands/pkg.js +++ b/lib/commands/pkg.js @@ -1,3 +1,4 @@ +const { inspect } = require('node:util') const { output } = require('proc-log') const PackageJson = require('@npmcli/package-json') const BaseCommand = require('../base-cmd.js') @@ -56,25 +57,40 @@ class Pkg extends BaseCommand { } async get (args, { path, workspace }) { - this.npm.config.set('json', true) const pkgJson = await PackageJson.load(path) + const json = this.npm.config.get('json') - let result = pkgJson.content + // filter out the newline/indent symbols from the package-json object + let result = JSON.parse(JSON.stringify(pkgJson.content)) if (args.length) { - result = new Queryable(result).query(args) - // in case there's only a single argument and a single result from the query just prints that one element to stdout. - // TODO(BREAKING_CHANGE): much like other places where we unwrap single item arrays this should go away. - // it makes the behavior unknown for users who don't already know the shape of the data. - if (Object.keys(result).length === 1 && args.length === 1) { - result = result[args] + result = new Queryable(result).query(args, { unwrapSingleItemArrays: false }) + if (args.length === 1 && !json && args[0] in result) { + if (workspace) { + return output.standard(`${workspace} ${result[args[0]]}`) + } + return output.standard(result[args[0]]) } } - // The display layer is responsible for calling JSON.stringify on the result - // TODO: https://github.com/npm/cli/issues/5508 a raw mode has been requested similar to jq -r. - // If that was added then this method should no longer set `json:true` all the time - output.buffer(workspace ? { [workspace]: result } : result) + if (json) { + output.buffer(workspace ? { [workspace]: result } : result) + } else { + for (let [f, d] of Object.entries(result)) { + d = inspect(d, { + showHidden: false, + depth: 5, + colors: this.npm.color, + maxArrayLength: null, + }) + + if (workspace) { + output.standard(`${workspace} ${f} = ${d}`) + } else { + output.standard(`${f} = ${d}`) + } + } + } } async set (args, { path }) { diff --git a/lib/commands/view.js b/lib/commands/view.js index 28393c823d65f..6656df9873e2c 100644 --- a/lib/commands/view.js +++ b/lib/commands/view.js @@ -204,6 +204,7 @@ class View extends BaseCommand { const includeVersions = versions.length > 1 let includeFields + // TODO if we ask for two fields but only one existed we treat it as if we only asked for one field, this needs to be fixed const res = versions.flatMap((v) => { const fields = Object.entries(data[v]) diff --git a/smoke-tests/tap-snapshots/test/index.js.test.cjs b/smoke-tests/tap-snapshots/test/index.js.test.cjs index b3247b148a40b..3d99de9a5bea7 100644 --- a/smoke-tests/tap-snapshots/test/index.js.test.cjs +++ b/smoke-tests/tap-snapshots/test/index.js.test.cjs @@ -344,7 +344,7 @@ exports[`test/index.js TAP basic npm pkg > should have expected pkg delete outpu ` exports[`test/index.js TAP basic npm pkg > should have expected pkg get output 1`] = ` -"ISC" +ISC ` exports[`test/index.js TAP basic npm pkg > should have expected pkg set output 1`] = ` @@ -352,28 +352,20 @@ exports[`test/index.js TAP basic npm pkg > should have expected pkg set output 1 ` exports[`test/index.js TAP basic npm pkg > should print package.json contents 1`] = ` -{ - "name": "project", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo /"Error: no test specified/" && exit 1", - "hello": "echo Hello" - }, - "keywords": [], - "author": "", - "license": "ISC", - "type": "commonjs", - "dependencies": { - "abbrev": "^1.0.4" - }, - "tap": { - "test-env": [ - "LC_ALL=sk" - ] - } +name = 'project' +version = '1.0.0' +description = '' +main = 'index.js' +scripts = { + test: 'echo "Error: no test specified" && exit 1', + hello: 'echo Hello' } +keywords = [] +author = '' +license = 'ISC' +type = 'commonjs' +dependencies = { abbrev: '^1.0.4' } +tap = { 'test-env': [ 'LC_ALL=sk' ] } ` exports[`test/index.js TAP basic npm pkg set scripts > should have expected script added package.json result 1`] = ` diff --git a/tap-snapshots/test/lib/commands/pkg.js.test.cjs b/tap-snapshots/test/lib/commands/pkg.js.test.cjs index 95d9e6b86b0dd..9e31b675ec20d 100644 --- a/tap-snapshots/test/lib/commands/pkg.js.test.cjs +++ b/tap-snapshots/test/lib/commands/pkg.js.test.cjs @@ -5,13 +5,13 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`test/lib/commands/pkg.js TAP delete multiple field > should delete multiple fields from package.json 1`] = ` +exports[`test/lib/commands/pkg.js TAP delete delete multiple field > should delete multiple fields from package.json 1`] = ` Object { "name": "foo", } ` -exports[`test/lib/commands/pkg.js TAP delete nested field > should delete nested fields from package.json 1`] = ` +exports[`test/lib/commands/pkg.js TAP delete delete nested field > should delete nested fields from package.json 1`] = ` Object { "info": Object { "foo": Object { @@ -25,7 +25,7 @@ Object { } ` -exports[`test/lib/commands/pkg.js TAP delete single field > should delete single field from package.json 1`] = ` +exports[`test/lib/commands/pkg.js TAP delete delete single field > should delete single field from package.json 1`] = ` Object { "name": "foo", } @@ -39,59 +39,63 @@ Object { ` exports[`test/lib/commands/pkg.js TAP get array field > should print retrieved array field 1`] = ` -[ - "index.js", - "cli.js" -] +[ 'index.js', 'cli.js' ] ` exports[`test/lib/commands/pkg.js TAP get array item > should print retrieved array field 1`] = ` - +index.js ` -exports[`test/lib/commands/pkg.js TAP get array nested items notation > should print json result containing matching results 1`] = ` -{ - "contributors[0].name": "Ruy", - "contributors[1].name": "Gar" -} +exports[`test/lib/commands/pkg.js TAP get get array nested items notation > should print json result containing matching results 1`] = ` +contributors[0].name = 'Ruy' +contributors[1].name = 'Gar' ` -exports[`test/lib/commands/pkg.js TAP get multiple arg > should print retrieved package.json fields 1`] = ` +exports[`test/lib/commands/pkg.js TAP get json no args > should print package.json content 1`] = ` { "name": "foo", "version": "1.1.1" } ` -exports[`test/lib/commands/pkg.js TAP get multiple arg with empty value > should print retrieved package.json field regardless of empty value 1`] = ` +exports[`test/lib/commands/pkg.js TAP get json with args > should print package.json content 1`] = ` { - "name": "foo", - "author": "" + "name": "foo" } ` +exports[`test/lib/commands/pkg.js TAP get multiple arg > should print retrieved package.json fields 1`] = ` +name = 'foo' +version = '1.1.1' +` + +exports[`test/lib/commands/pkg.js TAP get multiple arg with empty value > should print retrieved package.json field regardless of empty value 1`] = ` +name = 'foo' +author = '' +` + exports[`test/lib/commands/pkg.js TAP get multiple arg with only one arg existing > should print retrieved package.json field 1`] = ` -{ - "name": "foo" -} +name = 'foo' ` exports[`test/lib/commands/pkg.js TAP get nested arg > node test.js 1`] = ` -"node test.js" +node test.js ` exports[`test/lib/commands/pkg.js TAP get no args > should print package.json content 1`] = ` -{ - "name": "foo", - "version": "1.1.1" -} +name = 'foo' +version = '1.1.1' +` + +exports[`test/lib/commands/pkg.js TAP get non string > should print retrieved package.json field 1`] = ` +{ '@npmcli/test': '*' } ` exports[`test/lib/commands/pkg.js TAP get single arg > should print retrieved package.json field 1`] = ` -"1.1.1" +1.1.1 ` -exports[`test/lib/commands/pkg.js TAP push to array syntax > should append to arrays using empty bracket syntax 1`] = ` +exports[`test/lib/commands/pkg.js TAP set push to array syntax > should append to arrays using empty bracket syntax 1`] = ` Object { "keywords": Array [ "foo", @@ -103,7 +107,7 @@ Object { } ` -exports[`test/lib/commands/pkg.js TAP set --json > should add fields to package.json 1`] = ` +exports[`test/lib/commands/pkg.js TAP set set --json > should add fields to package.json 1`] = ` Object { "description": "awesome", "foo": Object { @@ -123,7 +127,7 @@ Object { } ` -exports[`test/lib/commands/pkg.js TAP set = separate value > should add single field to package.json 1`] = ` +exports[`test/lib/commands/pkg.js TAP set set = separate value > should add single field to package.json 1`] = ` Object { "name": "foo", "tap": Object { @@ -135,7 +139,7 @@ Object { } ` -exports[`test/lib/commands/pkg.js TAP set multiple fields > should add single field to package.json 1`] = ` +exports[`test/lib/commands/pkg.js TAP set set multiple fields > should add single field to package.json 1`] = ` Object { "bin": Object { "foo": "foo.js", @@ -148,7 +152,7 @@ Object { } ` -exports[`test/lib/commands/pkg.js TAP set single field > should add single field to package.json 1`] = ` +exports[`test/lib/commands/pkg.js TAP set set single field > should add single field to package.json 1`] = ` Object { "description": "Awesome stuff", "name": "foo", @@ -157,21 +161,22 @@ Object { ` exports[`test/lib/commands/pkg.js TAP single workspace multiple args > should only return info for one workspace 1`] = ` -{ - "a": { - "name": "a", - "version": "1.0.0" - } -} +a name = 'a' +a version = '1.0.0' ` exports[`test/lib/commands/pkg.js TAP single workspace single arg > should only return info for one workspace 1`] = ` -{ - "a": "1.0.0" -} +a 1.0.0 ` exports[`test/lib/commands/pkg.js TAP workspaces get > should return expected result for configured workspaces 1`] = ` +a name = 'a' +a version = '1.0.0' +b name = 'b' +b version = '1.2.3' +` + +exports[`test/lib/commands/pkg.js TAP workspaces get json > should return expected json result for configured workspaces 1`] = ` { "a": { "name": "a", diff --git a/test/lib/commands/pkg.js b/test/lib/commands/pkg.js index 3d176617d445f..50288cc42366b 100644 --- a/test/lib/commands/pkg.js +++ b/test/lib/commands/pkg.js @@ -7,7 +7,7 @@ const { cleanCwd } = require('../../fixtures/clean-snapshot') t.cleanSnapshot = (str) => cleanCwd(str) const readPackageJson = (prefix, dir = '') => - JSON.parse(readFileSync(resolve(prefix, dir, 'package.json'), 'utf8')) + JSON.parse(readFileSync(resolve(prefix, dir, 'package.json'), 'utf8')) t.test('no args', async t => { const { npm } = await loadMockNpm(t) @@ -22,8 +22,8 @@ t.test('no args', async t => { t.test('no global mode', async t => { const { npm } = await loadMockNpm(t, { config: { - global: true - } + global: true, + }, }) await t.rejects( @@ -33,405 +33,474 @@ t.test('no global mode', async t => { ) }) -t.test('get no args', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - }), - }, +t.test('get', t => { + t.test('no args', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + }), + }, + }) + await npm.exec('pkg', ['get']) + + t.matchSnapshot( + joinedOutput(), + 'should print package.json content' + ) }) - await npm.exec('pkg', ['get']) - t.matchSnapshot( - joinedOutput(), - 'should print package.json content' - ) -}) + t.test('single arg', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + }), + }, + }) -t.test('get single arg', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - }), - }, + await npm.exec('pkg', ['get', 'version']) + + t.matchSnapshot( + joinedOutput(), + 'should print retrieved package.json field' + ) }) - await npm.exec('pkg', ['get', 'version']) + t.test('non string', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + dependencies: { + '@npmcli/test': '*', + }, + }), + }, + }) - t.matchSnapshot( - joinedOutput(), - 'should print retrieved package.json field' - ) -}) + await npm.exec('pkg', ['get', 'dependencies']) -t.test('get multiple arg', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - }), - }, + t.matchSnapshot( + joinedOutput(), + 'should print retrieved package.json field' + ) }) + t.test('multiple arg', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + }), + }, + }) - await npm.exec('pkg', ['get', 'name', 'version']) + await npm.exec('pkg', ['get', 'name', 'version']) - t.matchSnapshot( - joinedOutput(), - 'should print retrieved package.json fields' - ) -}) + t.matchSnapshot( + joinedOutput(), + 'should print retrieved package.json fields' + ) + }) -t.test('get multiple arg with only one arg existing', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - }), - }, + t.test('multiple arg with only one arg existing', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + }), + }, + }) + + await npm.exec('pkg', ['get', 'name', 'version', 'dependencies']) + + t.matchSnapshot( + joinedOutput(), + 'should print retrieved package.json field' + ) }) - await npm.exec('pkg', ['get', 'name', 'version', 'dependencies']) + t.test('multiple arg with empty value', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + author: '', + }), + }, + }) - t.matchSnapshot( - joinedOutput(), - 'should print retrieved package.json field' - ) -}) + await npm.exec('pkg', ['get', 'name', 'author']) -t.test('get multiple arg with empty value', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - author: '', - }), - }, + t.matchSnapshot( + joinedOutput(), + 'should print retrieved package.json field regardless of empty value' + ) }) - await npm.exec('pkg', ['get', 'name', 'author']) + t.test('nested arg', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + scripts: { + test: 'node test.js', + }, + }), + }, + }) - t.matchSnapshot( - joinedOutput(), - 'should print retrieved package.json field regardless of empty value' - ) -}) + await npm.exec('pkg', ['get', 'scripts.test']) -t.test('get nested arg', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - scripts: { - test: 'node test.js', - }, - }), - }, + t.matchSnapshot( + joinedOutput(), + 'node test.js', + 'should print retrieved nested field' + ) }) - await npm.exec('pkg', ['get', 'scripts.test']) + t.test('array field', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + files: [ + 'index.js', + 'cli.js', + ], + }), + }, + }) - t.matchSnapshot( - joinedOutput(), - 'node test.js', - 'should print retrieved nested field' - ) -}) + await npm.exec('pkg', ['get', 'files']) -t.test('get array field', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - files: [ - 'index.js', - 'cli.js', - ], - }), - }, + t.matchSnapshot( + joinedOutput(), + 'should print retrieved array field' + ) }) - await npm.exec('pkg', ['get', 'files']) + t.test('array item', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + files: [ + 'index.js', + 'cli.js', + ], + }), + }, + }) - t.matchSnapshot( - joinedOutput(), - 'should print retrieved array field' - ) -}) + await npm.exec('pkg', ['get', 'files[0]']) -t.test('get array item', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - files: [, - 'index.js', - 'cli.js', - ], - }), - }, + t.matchSnapshot( + joinedOutput(), + 'should print retrieved array field' + ) }) - await npm.exec('pkg', ['get', 'files[0]']) + t.test('json no args', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + }), + }, + config: { + json: true, + }, + }) + await npm.exec('pkg', ['get']) - t.matchSnapshot( - joinedOutput(), - 'should print retrieved array field' - ) -}) + t.matchSnapshot( + joinedOutput(), + 'should print package.json content' + ) + }) -t.test('get array nested items notation', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - contributors: [ - { - name: 'Ruy', - url: 'http://example.com/ruy', - }, - { - name: 'Gar', - url: 'http://example.com/gar', - }, - ], - }), - }, + t.test('json with args', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + }), + }, + config: { + json: true, + }, + }) + await npm.exec('pkg', ['get', 'name']) + + t.matchSnapshot( + joinedOutput(), + 'should print package.json content' + ) }) - await npm.exec('pkg', ['get', 'contributors.name']) - t.matchSnapshot( - joinedOutput(), - 'should print json result containing matching results' - ) -}) + t.test('get array nested items notation', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + contributors: [ + { + name: 'Ruy', + url: 'http://example.com/ruy', + }, + { + name: 'Gar', + url: 'http://example.com/gar', + }, + ], + }), + }, + }) -t.test('set no args', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ name: 'foo' }), - }, + await npm.exec('pkg', ['get', 'contributors.name']) + t.matchSnapshot( + joinedOutput(), + 'should print json result containing matching results' + ) }) - await t.rejects( - npm.exec('pkg', ['set']), - { code: 'EUSAGE' }, - 'should throw an error if no args' - ) + t.end() }) -t.test('set missing value', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ name: 'foo' }), - }, +t.test('set', t => { + t.test('set no args', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'foo' }), + }, + }) + await t.rejects( + npm.exec('pkg', ['set']), + { code: 'EUSAGE' }, + 'should throw an error if no args' + ) }) - await t.rejects( - npm.exec('pkg', ['set', 'key=']), - { code: 'EUSAGE' }, - 'should throw an error if missing value' - ) -}) -t.test('set missing key', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ name: 'foo' }), - }, + t.test('set missing value', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'foo' }), + }, + }) + await t.rejects( + npm.exec('pkg', ['set', 'key=']), + { code: 'EUSAGE' }, + 'should throw an error if missing value' + ) }) - await t.rejects( - npm.exec('pkg', ['set', '=value']), - { code: 'EUSAGE' }, - 'should throw an error if missing key' - ) -}) -t.test('set single field', async t => { - const json = { - name: 'foo', - version: '1.1.1', - } - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify(json), - }, + t.test('set missing key', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'foo' }), + }, + }) + await t.rejects( + npm.exec('pkg', ['set', '=value']), + { code: 'EUSAGE' }, + 'should throw an error if missing key' + ) }) - await npm.exec('pkg', ['set', 'description=Awesome stuff']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should add single field to package.json' - ) -}) + t.test('set single field', async t => { + const json = { + name: 'foo', + version: '1.1.1', + } + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify(json), + }, + }) -t.test('push to array syntax', async t => { - const json = { - name: 'foo', - version: '1.1.1', - keywords: [ - 'foo', - ], - } - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify(json), - }, + await npm.exec('pkg', ['set', 'description=Awesome stuff']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should add single field to package.json' + ) }) - await npm.exec('pkg', ['set', 'keywords[]=bar', 'keywords[]=baz']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should append to arrays using empty bracket syntax' - ) -}) + t.test('push to array syntax', async t => { + const json = { + name: 'foo', + version: '1.1.1', + keywords: [ + 'foo', + ], + } + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify(json), + }, + }) -t.test('set multiple fields', async t => { - const json = { - name: 'foo', - version: '1.1.1', - } - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify(json), - }, + await npm.exec('pkg', ['set', 'keywords[]=bar', 'keywords[]=baz']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should append to arrays using empty bracket syntax' + ) }) - await npm.exec('pkg', ['set', 'bin.foo=foo.js', 'scripts.test=node test.js']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should add single field to package.json' - ) -}) + t.test('set multiple fields', async t => { + const json = { + name: 'foo', + version: '1.1.1', + } + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify(json), + }, + }) -t.test('set = separate value', async t => { - const json = { - name: 'foo', - version: '1.1.1', - } - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify(json), - }, + await npm.exec('pkg', ['set', 'bin.foo=foo.js', 'scripts.test=node test.js']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should add single field to package.json' + ) }) - await npm.exec('pkg', ['set', 'tap[test-env][0]=LC_ALL=sk']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should add single field to package.json' - ) -}) + t.test('set = separate value', async t => { + const json = { + name: 'foo', + version: '1.1.1', + } + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify(json), + }, + }) -t.test('set --json', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.1.1', - }), - }, - config: { json: true }, + await npm.exec('pkg', ['set', 'tap[test-env][0]=LC_ALL=sk']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should add single field to package.json' + ) }) - await npm.exec('pkg', ['set', 'private=true']) - await npm.exec('pkg', ['set', 'tap.timeout=60']) - await npm.exec('pkg', ['set', 'foo={ "bar": { "baz": "BAZ" } }']) - await npm.exec('pkg', ['set', 'workspaces=["packages/*"]']) - await npm.exec('pkg', ['set', 'description="awesome"']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should add fields to package.json' - ) + t.test('set --json', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.1.1', + }), + }, + config: { json: true }, + }) + + await npm.exec('pkg', ['set', 'private=true']) + await npm.exec('pkg', ['set', 'tap.timeout=60']) + await npm.exec('pkg', ['set', 'foo={ "bar": { "baz": "BAZ" } }']) + await npm.exec('pkg', ['set', 'workspaces=["packages/*"]']) + await npm.exec('pkg', ['set', 'description="awesome"']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should add fields to package.json' + ) + }) + t.end() }) -t.test('delete no args', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ name: 'foo' }), - }, +t.test('delete', t => { + t.test('delete no args', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'foo' }), + }, + }) + await t.rejects( + npm.exec('pkg', ['delete']), + { code: 'EUSAGE' }, + 'should throw an error if deleting no args' + ) }) - await t.rejects( - npm.exec('pkg', ['delete']), - { code: 'EUSAGE' }, - 'should throw an error if deleting no args' - ) -}) -t.test('delete invalid key', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ name: 'foo' }), - }, + t.test('delete invalid key', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'foo' }), + }, + }) + await t.rejects( + npm.exec('pkg', ['delete', '']), + { code: 'EUSAGE' }, + 'should throw an error if deleting invalid args' + ) }) - await t.rejects( - npm.exec('pkg', ['delete', '']), - { code: 'EUSAGE' }, - 'should throw an error if deleting invalid args' - ) -}) -t.test('delete single field', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.0.0', - }), - }, + t.test('delete single field', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + }), + }, + }) + await npm.exec('pkg', ['delete', 'version']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should delete single field from package.json' + ) }) - await npm.exec('pkg', ['delete', 'version']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should delete single field from package.json' - ) -}) -t.test('delete multiple field', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.0.0', - description: 'awesome', - }), - }, + t.test('delete multiple field', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + description: 'awesome', + }), + }, + }) + await npm.exec('pkg', ['delete', 'version', 'description']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should delete multiple fields from package.json' + ) }) - await npm.exec('pkg', ['delete', 'version', 'description']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should delete multiple fields from package.json' - ) -}) -t.test('delete nested field', async t => { - const { npm } = await loadMockNpm(t, { - prefixDir: { - 'package.json': JSON.stringify({ - name: 'foo', - version: '1.0.0', - info: { - foo: { - bar: [ - { - baz: 'deleteme', - }, - ], + t.test('delete nested field', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + info: { + foo: { + bar: [ + { + baz: 'deleteme', + }, + ], + }, }, - }, - }), - }, + }), + }, + }) + await npm.exec('pkg', ['delete', 'info.foo.bar[0].baz']) + t.matchSnapshot( + readPackageJson(npm.prefix), + 'should delete nested fields from package.json' + ) }) - await npm.exec('pkg', ['delete', 'info.foo.bar[0].baz']) - t.matchSnapshot( - readPackageJson(npm.prefix), - 'should delete nested fields from package.json' - ) + t.end() }) t.test('workspaces', async t => { @@ -471,8 +540,23 @@ t.test('workspaces', async t => { ) }) + t.test('get json ', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + ...workspaceSetup, + config: { + json: true, + workspaces: true, + }, + }) + await npm.exec('pkg', ['get', 'name', 'version']) + t.matchSnapshot( + joinedOutput(), + 'should return expected json result for configured workspaces' + ) + }) + t.test('set', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, workspaceSetup) + const { npm } = await loadMockNpm(t, workspaceSetup) await npm.exec('pkg', ['set', 'funding=http://example.com']) @@ -550,7 +634,7 @@ t.test('single workspace', async t => { }) t.test('fix', async t => { - const { npm, joinedOutput } = await loadMockNpm(t, { + const { npm } = await loadMockNpm(t, { prefixDir: { 'package.json': JSON.stringify({ name: 'foo ',