xref: /third_party/node/deps/npm/test/lib/commands/pkg.js (revision 1cb0ef41)
1const { resolve } = require('path')
2const { readFileSync } = require('fs')
3const t = require('tap')
4const _mockNpm = require('../../fixtures/mock-npm')
5const { cleanCwd } = require('../../fixtures/clean-snapshot')
6
7t.cleanSnapshot = (str) => cleanCwd(str)
8
9const mockNpm = async (t, { ...opts } = {}) => {
10  const res = await _mockNpm(t, opts)
11
12  const readPackageJson = (dir = '') =>
13    JSON.parse(readFileSync(resolve(res.prefix, dir, 'package.json'), 'utf8'))
14
15  return {
16    ...res,
17    pkg: (...args) => res.npm.exec('pkg', args),
18    readPackageJson,
19    OUTPUT: () => res.joinedOutput(),
20  }
21}
22
23t.test('no args', async t => {
24  const { pkg } = await mockNpm(t)
25
26  await t.rejects(
27    pkg(),
28    { code: 'EUSAGE' },
29    'should throw usage error'
30  )
31})
32
33t.test('no global mode', async t => {
34  const { pkg } = await mockNpm(t, {
35    config: { global: true },
36  })
37
38  await t.rejects(
39    pkg('get', 'foo'),
40    { code: 'EPKGGLOBAL' },
41    'should throw no global mode error'
42  )
43})
44
45t.test('get no args', async t => {
46  const { pkg, OUTPUT } = await mockNpm(t, {
47    prefixDir: {
48      'package.json': JSON.stringify({
49        name: 'foo',
50        version: '1.1.1',
51      }),
52    },
53  })
54
55  await pkg('get')
56
57  t.strictSame(
58    JSON.parse(OUTPUT()),
59    {
60      name: 'foo',
61      version: '1.1.1',
62    },
63    'should print package.json content'
64  )
65})
66
67t.test('get single arg', async t => {
68  const { pkg, OUTPUT } = await mockNpm(t, {
69    prefixDir: {
70      'package.json': JSON.stringify({
71        name: 'foo',
72        version: '1.1.1',
73      }),
74    },
75  })
76
77  await pkg('get', 'version')
78
79  t.strictSame(
80    JSON.parse(OUTPUT()),
81    '1.1.1',
82    'should print retrieved package.json field'
83  )
84})
85
86t.test('get multiple arg', async t => {
87  const { pkg, OUTPUT } = await mockNpm(t, {
88    prefixDir: {
89      'package.json': JSON.stringify({
90        name: 'foo',
91        version: '1.1.1',
92      }),
93    },
94  })
95
96  await pkg('get', 'name', 'version')
97
98  t.strictSame(
99    JSON.parse(OUTPUT()),
100    {
101      name: 'foo',
102      version: '1.1.1',
103    },
104    'should print retrieved package.json field'
105  )
106})
107
108t.test('get multiple arg with empty value', async t => {
109  const { pkg, OUTPUT } = await mockNpm(t, {
110    prefixDir: {
111      'package.json': JSON.stringify({
112        name: 'foo',
113        author: '',
114      }),
115    },
116  })
117
118  await pkg('get', 'name', 'author')
119
120  t.strictSame(
121    JSON.parse(OUTPUT()),
122    {
123      name: 'foo',
124      author: '',
125    },
126    'should print retrieved package.json field regardless of empty value'
127  )
128})
129
130t.test('get nested arg', async t => {
131  const { pkg, OUTPUT } = await mockNpm(t, {
132    prefixDir: {
133      'package.json': JSON.stringify({
134        name: 'foo',
135        version: '1.1.1',
136        scripts: {
137          test: 'node test.js',
138        },
139      }),
140    },
141  })
142
143  await pkg('get', 'scripts.test')
144
145  t.strictSame(
146    JSON.parse(OUTPUT()),
147    'node test.js',
148    'should print retrieved nested field'
149  )
150})
151
152t.test('get array field', async t => {
153  const files = [
154    'index.js',
155    'cli.js',
156  ]
157  const { pkg, OUTPUT } = await mockNpm(t, {
158    prefixDir: {
159      'package.json': JSON.stringify({
160        name: 'foo',
161        version: '1.1.1',
162        files,
163      }),
164    },
165  })
166
167  await pkg('get', 'files')
168
169  t.strictSame(
170    JSON.parse(OUTPUT()),
171    files,
172    'should print retrieved array field'
173  )
174})
175
176t.test('get array item', async t => {
177  const files = [
178    'index.js',
179    'cli.js',
180  ]
181  const { pkg, OUTPUT } = await mockNpm(t, {
182    prefixDir: {
183      'package.json': JSON.stringify({
184        name: 'foo',
185        version: '1.1.1',
186        files,
187      }),
188    },
189  })
190
191  await pkg('get', 'files[0]')
192
193  t.strictSame(
194    JSON.parse(OUTPUT()),
195    'index.js',
196    'should print retrieved array field'
197  )
198})
199
200t.test('get array nested items notation', async t => {
201  const contributors = [
202    {
203      name: 'Ruy',
204      url: 'http://example.com/ruy',
205    },
206    {
207      name: 'Gar',
208      url: 'http://example.com/gar',
209    },
210  ]
211  const { pkg, OUTPUT } = await mockNpm(t, {
212    prefixDir: {
213      'package.json': JSON.stringify({
214        name: 'foo',
215        version: '1.1.1',
216        contributors,
217      }),
218    },
219  })
220
221  await pkg('get', 'contributors.name')
222  t.strictSame(
223    JSON.parse(OUTPUT()),
224    {
225      'contributors[0].name': 'Ruy',
226      'contributors[1].name': 'Gar',
227    },
228    'should print json result containing matching results'
229  )
230})
231
232t.test('set no args', async t => {
233  const { pkg } = await mockNpm(t, {
234    prefixDir: {
235      'package.json': JSON.stringify({ name: 'foo' }),
236    },
237  })
238  await t.rejects(
239    pkg('set'),
240    { code: 'EUSAGE' },
241    'should throw an error if no args'
242  )
243})
244
245t.test('set missing value', async t => {
246  const { pkg } = await mockNpm(t, {
247    prefixDir: {
248      'package.json': JSON.stringify({ name: 'foo' }),
249    },
250  })
251  await t.rejects(
252    pkg('set', 'key='),
253    { code: 'EUSAGE' },
254    'should throw an error if missing value'
255  )
256})
257
258t.test('set missing key', async t => {
259  const { pkg } = await mockNpm(t, {
260    prefixDir: {
261      'package.json': JSON.stringify({ name: 'foo' }),
262    },
263  })
264  await t.rejects(
265    pkg('set', '=value'),
266    { code: 'EUSAGE' },
267    'should throw an error if missing key'
268  )
269})
270
271t.test('set single field', async t => {
272  const json = {
273    name: 'foo',
274    version: '1.1.1',
275  }
276  const { pkg, readPackageJson } = await mockNpm(t, {
277    prefixDir: {
278      'package.json': JSON.stringify(json),
279    },
280  })
281
282  await pkg('set', 'description=Awesome stuff')
283  t.strictSame(
284    readPackageJson(),
285    {
286      ...json,
287      description: 'Awesome stuff',
288    },
289    'should add single field to package.json'
290  )
291})
292
293t.test('push to array syntax', async t => {
294  const json = {
295    name: 'foo',
296    version: '1.1.1',
297    keywords: [
298      'foo',
299    ],
300  }
301  const { pkg, readPackageJson } = await mockNpm(t, {
302    prefixDir: {
303      'package.json': JSON.stringify(json),
304    },
305  })
306
307  await pkg('set', 'keywords[]=bar', 'keywords[]=baz')
308  t.strictSame(
309    readPackageJson(),
310    {
311      ...json,
312      keywords: [
313        'foo',
314        'bar',
315        'baz',
316      ],
317    },
318    'should append to arrays using empty bracket syntax'
319  )
320})
321
322t.test('set multiple fields', async t => {
323  const json = {
324    name: 'foo',
325    version: '1.1.1',
326  }
327  const { pkg, readPackageJson } = await mockNpm(t, {
328    prefixDir: {
329      'package.json': JSON.stringify(json),
330    },
331  })
332
333  await pkg('set', 'bin.foo=foo.js', 'scripts.test=node test.js')
334  t.strictSame(
335    readPackageJson(),
336    {
337      ...json,
338      bin: {
339        foo: 'foo.js',
340      },
341      scripts: {
342        test: 'node test.js',
343      },
344    },
345    'should add single field to package.json'
346  )
347})
348
349t.test('set = separate value', async t => {
350  const json = {
351    name: 'foo',
352    version: '1.1.1',
353  }
354  const { pkg, readPackageJson } = await mockNpm(t, {
355    prefixDir: {
356      'package.json': JSON.stringify(json),
357    },
358  })
359
360  await pkg('set', 'tap[test-env][0]=LC_ALL=sk')
361  t.strictSame(
362    readPackageJson(),
363    {
364      ...json,
365      tap: {
366        'test-env': [
367          'LC_ALL=sk',
368        ],
369      },
370    },
371    'should add single field to package.json'
372  )
373})
374
375t.test('set --json', async t => {
376  const { pkg, readPackageJson } = await mockNpm(t, {
377    prefixDir: {
378      'package.json': JSON.stringify({
379        name: 'foo',
380        version: '1.1.1',
381      }),
382    },
383    config: { json: true },
384  })
385
386  await pkg('set', 'private=true')
387  t.strictSame(
388    readPackageJson(),
389    {
390      name: 'foo',
391      version: '1.1.1',
392      private: true,
393    },
394    'should add boolean field to package.json'
395  )
396
397  await pkg('set', 'tap.timeout=60')
398  t.strictSame(
399    readPackageJson(),
400    {
401      name: 'foo',
402      version: '1.1.1',
403      private: true,
404      tap: {
405        timeout: 60,
406      },
407    },
408    'should add number field to package.json'
409  )
410
411  await pkg('set', 'foo={ "bar": { "baz": "BAZ" } }')
412  t.strictSame(
413    readPackageJson(),
414    {
415      name: 'foo',
416      version: '1.1.1',
417      private: true,
418      tap: {
419        timeout: 60,
420      },
421      foo: {
422        bar: {
423          baz: 'BAZ',
424        },
425      },
426    },
427    'should add object field to package.json'
428  )
429
430  await pkg('set', 'workspaces=["packages/*"]')
431  t.strictSame(
432    readPackageJson(),
433    {
434      name: 'foo',
435      version: '1.1.1',
436      private: true,
437      workspaces: [
438        'packages/*',
439      ],
440      tap: {
441        timeout: 60,
442      },
443      foo: {
444        bar: {
445          baz: 'BAZ',
446        },
447      },
448    },
449    'should add object field to package.json'
450  )
451
452  await pkg('set', 'description="awesome"')
453  t.strictSame(
454    readPackageJson(),
455    {
456      name: 'foo',
457      version: '1.1.1',
458      description: 'awesome',
459      private: true,
460      workspaces: [
461        'packages/*',
462      ],
463      tap: {
464        timeout: 60,
465      },
466      foo: {
467        bar: {
468          baz: 'BAZ',
469        },
470      },
471    },
472    'should add object field to package.json'
473  )
474})
475
476t.test('delete no args', async t => {
477  const { pkg } = await mockNpm(t, {
478    prefixDir: {
479      'package.json': JSON.stringify({ name: 'foo' }),
480    },
481  })
482  await t.rejects(
483    pkg('delete'),
484    { code: 'EUSAGE' },
485    'should throw an error if deleting no args'
486  )
487})
488
489t.test('delete invalid key', async t => {
490  const { pkg } = await mockNpm(t, {
491    prefixDir: {
492      'package.json': JSON.stringify({ name: 'foo' }),
493    },
494  })
495  await t.rejects(
496    pkg('delete', ''),
497    { code: 'EUSAGE' },
498    'should throw an error if deleting invalid args'
499  )
500})
501
502t.test('delete single field', async t => {
503  const { pkg, readPackageJson } = await mockNpm(t, {
504    prefixDir: {
505      'package.json': JSON.stringify({
506        name: 'foo',
507        version: '1.0.0',
508      }),
509    },
510  })
511  await pkg('delete', 'version')
512  t.strictSame(
513    readPackageJson(),
514    {
515      name: 'foo',
516    },
517    'should delete single field from package.json'
518  )
519})
520
521t.test('delete multiple field', async t => {
522  const { pkg, readPackageJson } = await mockNpm(t, {
523    prefixDir: {
524      'package.json': JSON.stringify({
525        name: 'foo',
526        version: '1.0.0',
527        description: 'awesome',
528      }),
529    },
530  })
531  await pkg('delete', 'version', 'description')
532  t.strictSame(
533    readPackageJson(),
534    {
535      name: 'foo',
536    },
537    'should delete multiple fields from package.json'
538  )
539})
540
541t.test('delete nested field', async t => {
542  const { pkg, readPackageJson } = await mockNpm(t, {
543    prefixDir: {
544      'package.json': JSON.stringify({
545        name: 'foo',
546        version: '1.0.0',
547        info: {
548          foo: {
549            bar: [
550              {
551                baz: 'deleteme',
552              },
553            ],
554          },
555        },
556      }),
557    },
558  })
559  await pkg('delete', 'info.foo.bar[0].baz')
560  t.strictSame(
561    readPackageJson(),
562    {
563      name: 'foo',
564      version: '1.0.0',
565      info: {
566        foo: {
567          bar: [
568            {},
569          ],
570        },
571      },
572    },
573    'should delete nested fields from package.json'
574  )
575})
576
577t.test('workspaces', async t => {
578  const { pkg, OUTPUT, readPackageJson } = await mockNpm(t, {
579    prefixDir: {
580      'package.json': JSON.stringify({
581        name: 'root',
582        version: '1.0.0',
583        workspaces: [
584          'packages/*',
585        ],
586      }),
587      packages: {
588        a: {
589          'package.json': JSON.stringify({
590            name: 'a',
591            version: '1.0.0',
592          }),
593        },
594        b: {
595          'package.json': JSON.stringify({
596            name: 'b',
597            version: '1.2.3',
598          }),
599        },
600      },
601    },
602    config: { workspaces: true },
603  })
604
605  await pkg('get', 'name', 'version')
606
607  t.strictSame(
608    JSON.parse(OUTPUT()),
609    {
610      a: {
611        name: 'a',
612        version: '1.0.0',
613      },
614      b: {
615        name: 'b',
616        version: '1.2.3',
617      },
618    },
619    'should return expected result for configured workspaces'
620  )
621
622  await pkg('set', 'funding=http://example.com')
623
624  t.strictSame(
625    readPackageJson('packages/a'),
626    {
627      name: 'a',
628      version: '1.0.0',
629      funding: 'http://example.com',
630    },
631    'should add field to workspace a'
632  )
633
634  t.strictSame(
635    readPackageJson('packages/b'),
636    {
637      name: 'b',
638      version: '1.2.3',
639      funding: 'http://example.com',
640    },
641    'should add field to workspace b'
642  )
643
644  await pkg('delete', 'version')
645
646  t.strictSame(
647    readPackageJson('packages/a'),
648    {
649      name: 'a',
650      funding: 'http://example.com',
651    },
652    'should delete version field from workspace a'
653  )
654
655  t.strictSame(
656    readPackageJson('packages/b'),
657    {
658      name: 'b',
659      funding: 'http://example.com',
660    },
661    'should delete version field from workspace b'
662  )
663})
664
665t.test('single workspace', async t => {
666  const { pkg, OUTPUT } = await mockNpm(t, {
667    prefixDir: {
668      'package.json': JSON.stringify({
669        name: 'root',
670        version: '1.0.0',
671        workspaces: [
672          'packages/*',
673        ],
674      }),
675      packages: {
676        a: {
677          'package.json': JSON.stringify({
678            name: 'a',
679            version: '1.0.0',
680          }),
681        },
682        b: {
683          'package.json': JSON.stringify({
684            name: 'b',
685            version: '1.2.3',
686          }),
687        },
688      },
689    },
690    config: { workspace: ['packages/a'] },
691  })
692
693  await pkg('get', 'name', 'version')
694
695  t.strictSame(
696    JSON.parse(OUTPUT()),
697    { a: { name: 'a', version: '1.0.0' } },
698    'should only return info for one workspace'
699  )
700})
701
702t.test('fix', async t => {
703  const { pkg, readPackageJson } = await mockNpm(t, {
704    prefixDir: {
705      'package.json': JSON.stringify({
706        name: 'foo ',
707        version: 'v1.1.1',
708      }),
709    },
710  })
711
712  await pkg('fix')
713  t.strictSame(
714    readPackageJson(),
715    { name: 'foo', version: '1.1.1' },
716    'fixes package.json issues'
717  )
718})
719