From 986ac2a25f31835b268e7fc48cfc2322df918d69 Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Fri, 10 May 2024 12:42:23 -0700 Subject: [PATCH] fix(view): dont unwrap arrays in json mode The view command alters the data by default to unwrap single item arrays when in human readable mode (the default). This change makes it so those arrays are not altered when the --json flag is set. Fixes #3611 --- lib/commands/view.js | 18 ++++---- lib/utils/queryable.js | 8 ++-- .../test/lib/commands/view.js.test.cjs | 22 ++++++++++ test/lib/commands/view.js | 42 +++++++++++++++++++ 4 files changed, 78 insertions(+), 12 deletions(-) diff --git a/lib/commands/view.js b/lib/commands/view.js index c0d5bf552eee0..155288c96ea85 100644 --- a/lib/commands/view.js +++ b/lib/commands/view.js @@ -175,6 +175,7 @@ class View extends BaseCommand { } async getData (pkg, args) { + const json = this.npm.config.get('json') const opts = { ...this.npm.flatOptions, preferOnline: true, @@ -228,7 +229,12 @@ class View extends BaseCommand { delete versions[v].readme } - data.push(showFields(pckmnt, versions[v], arg)) + data.push(showFields({ + data: pckmnt, + version: versions[v], + fields: arg, + json, + })) }) } }) @@ -242,11 +248,7 @@ class View extends BaseCommand { throw er } - if ( - !this.npm.config.get('json') && - args.length === 1 && - args[0] === '' - ) { + if (!json && args.length === 1 && args[0] === '') { pckmnt.version = version } @@ -432,7 +434,7 @@ function reducer (acc, cur) { } // return whatever was printed -function showFields (data, version, fields) { +function showFields ({ data, version, fields, json }) { const o = {} ;[data, version].forEach((s) => { Object.keys(s).forEach((k) => { @@ -441,7 +443,7 @@ function showFields (data, version, fields) { }) const queryable = new Queryable(o) - const s = queryable.query(fields) + const s = queryable.query(fields, { unwrapSingleItemArrays: !json }) const res = { [version.version]: s } if (s) { diff --git a/lib/utils/queryable.js b/lib/utils/queryable.js index 69621d928e8dd..372cde91e1ce0 100644 --- a/lib/utils/queryable.js +++ b/lib/utils/queryable.js @@ -83,7 +83,7 @@ const parseKeys = key => { return res } -const getter = ({ data, key }) => { +const getter = ({ data, key }, { unwrapSingleItemArrays = true } = {}) => { // keys are a list in which each entry represents the name of // a property that should be walked through the object in order to // return the final found value @@ -122,7 +122,7 @@ const getter = ({ data, key }) => { // these are some legacy expectations from // the old API consumed by lib/view.js - if (Array.isArray(_data) && _data.length <= 1) { + if (unwrapSingleItemArrays && Array.isArray(_data) && _data.length <= 1) { _data = _data[0] } @@ -243,7 +243,7 @@ class Queryable { this.#data = obj } - query (queries) { + query (queries, opts) { // this ugly interface here is meant to be a compatibility layer // with the legacy API lib/view.js is consuming, if at some point // we refactor that command then we can revisit making this nicer @@ -255,7 +255,7 @@ class Queryable { getter({ data: this.#data, key: query, - }) + }, opts) if (Array.isArray(queries)) { let res = {} diff --git a/tap-snapshots/test/lib/commands/view.js.test.cjs b/tap-snapshots/test/lib/commands/view.js.test.cjs index 3e06ecf5d054e..bf20ed4679a31 100644 --- a/tap-snapshots/test/lib/commands/view.js.test.cjs +++ b/tap-snapshots/test/lib/commands/view.js.test.cjs @@ -300,6 +300,28 @@ dist-tags: published over a year from now ` +exports[`test/lib/commands/view.js TAP package with single version full json > must match snapshot 1`] = ` +{ + "_id": "single-version", + "name": "single-version", + "dist-tags": { + "latest": "1.0.0" + }, + "time": { + "1.0.0": "2024-05-07T19:41:40.177Z" + }, + "versions": [ + "1.0.0" + ], + "version": "1.0.0", + "dist": { + "shasum": "123", + "tarball": "http://hm.single-version.com/1.0.0.tgz", + "fileCount": 1 + } +} +` + exports[`test/lib/commands/view.js TAP specific field names array field - 1 element > must match snapshot 1`] = ` claudia ` diff --git a/test/lib/commands/view.js b/test/lib/commands/view.js index c50668791bbe5..2b60cf9442d19 100644 --- a/test/lib/commands/view.js +++ b/test/lib/commands/view.js @@ -252,6 +252,27 @@ const packument = (nv, opts) => { }, }, }, + 'single-version': { + _id: 'single-version', + name: 'single-version', + 'dist-tags': { + latest: '1.0.0', + }, + time: { + '1.0.0': yesterday, + }, + versions: { + '1.0.0': { + name: 'single-version', + version: '1.0.0', + dist: { + shasum: '123', + tarball: 'http://hm.single-version.com/1.0.0.tgz', + fileCount: 1, + }, + }, + }, + }, } if (nv.type === 'git') { return mocks[nv.hosted.project] @@ -357,6 +378,27 @@ t.test('package with --json and no versions', async t => { t.equal(joinedOutput(), '', 'no info to display') }) +t.test('package with single version', async t => { + t.test('full json', async t => { + const { view, joinedOutput } = await loadMockNpm(t, { config: { json: true } }) + await view.exec(['single-version']) + t.matchSnapshot(joinedOutput()) + }) + + t.test('json and versions arg', async t => { + const { view, joinedOutput } = await loadMockNpm(t, { config: { json: true } }) + await view.exec(['single-version', 'versions']) + const parsed = JSON.parse(joinedOutput()) + t.strictSame(parsed, ['1.0.0'], 'does not unwrap single item arrays in json') + }) + + t.test('no json and versions arg', async t => { + const { view, joinedOutput } = await loadMockNpm(t, { config: { json: false } }) + await view.exec(['single-version', 'versions']) + t.strictSame(joinedOutput(), '1.0.0', 'unwraps single item arrays in basic mode') + }) +}) + t.test('package in cwd', async t => { const prefixDir = { 'package.json': JSON.stringify({