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