1'use strict' 2 3const crypto = require('crypto') 4const cloneDeep = require('lodash.clonedeep') 5const figgyPudding = require('figgy-pudding') 6const mockTar = require('./util/mock-tarball.js') 7const { PassThrough } = require('stream') 8const ssri = require('ssri') 9const { test } = require('tap') 10const tnock = require('./util/tnock.js') 11 12const publish = require('../publish.js') 13 14const OPTS = figgyPudding({ registry: {} })({ 15 registry: 'https://mock.reg/' 16}) 17 18const REG = OPTS.registry 19 20test('basic publish', t => { 21 const manifest = { 22 name: 'libnpmpublish', 23 version: '1.0.0', 24 description: 'some stuff' 25 } 26 return mockTar({ 27 'package.json': JSON.stringify(manifest), 28 'index.js': 'console.log("hello world")' 29 }).then(tarData => { 30 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 31 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 32 const packument = { 33 name: 'libnpmpublish', 34 description: 'some stuff', 35 readme: '', 36 _id: 'libnpmpublish', 37 'dist-tags': { 38 latest: '1.0.0' 39 }, 40 versions: { 41 '1.0.0': { 42 _id: 'libnpmpublish@1.0.0', 43 _nodeVersion: process.versions.node, 44 name: 'libnpmpublish', 45 version: '1.0.0', 46 description: 'some stuff', 47 dist: { 48 shasum, 49 integrity: integrity.toString(), 50 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 51 } 52 } 53 }, 54 _attachments: { 55 'libnpmpublish-1.0.0.tgz': { 56 'content_type': 'application/octet-stream', 57 data: tarData.toString('base64'), 58 length: tarData.length 59 } 60 } 61 } 62 const srv = tnock(t, REG) 63 srv.put('/libnpmpublish', body => { 64 t.deepEqual(body, packument, 'posted packument matches expectations') 65 return true 66 }, { 67 authorization: 'Bearer deadbeef' 68 }).reply(201, {}) 69 70 return publish(manifest, tarData, OPTS.concat({ 71 token: 'deadbeef' 72 })).then(ret => { 73 t.ok(ret, 'publish succeeded') 74 }) 75 }) 76}) 77 78test('scoped publish', t => { 79 const manifest = { 80 name: '@zkat/libnpmpublish', 81 version: '1.0.0', 82 description: 'some stuff' 83 } 84 return mockTar({ 85 'package.json': JSON.stringify(manifest), 86 'index.js': 'console.log("hello world")' 87 }).then(tarData => { 88 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 89 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 90 const packument = { 91 name: '@zkat/libnpmpublish', 92 description: 'some stuff', 93 readme: '', 94 _id: '@zkat/libnpmpublish', 95 'dist-tags': { 96 latest: '1.0.0' 97 }, 98 versions: { 99 '1.0.0': { 100 _id: '@zkat/libnpmpublish@1.0.0', 101 _nodeVersion: process.versions.node, 102 _npmVersion: '6.9.0', 103 name: '@zkat/libnpmpublish', 104 version: '1.0.0', 105 description: 'some stuff', 106 dist: { 107 shasum, 108 integrity: integrity.toString(), 109 tarball: `http://mock.reg/@zkat/libnpmpublish/-/@zkat/libnpmpublish-1.0.0.tgz` 110 } 111 } 112 }, 113 _attachments: { 114 '@zkat/libnpmpublish-1.0.0.tgz': { 115 'content_type': 'application/octet-stream', 116 data: tarData.toString('base64'), 117 length: tarData.length 118 } 119 } 120 } 121 const srv = tnock(t, REG) 122 srv.put('/@zkat%2flibnpmpublish', body => { 123 t.deepEqual(body, packument, 'posted packument matches expectations') 124 return true 125 }, { 126 authorization: 'Bearer deadbeef' 127 }).reply(201, {}) 128 129 return publish(manifest, tarData, OPTS.concat({ 130 npmVersion: '6.9.0', 131 token: 'deadbeef' 132 })).then(() => { 133 t.ok(true, 'publish succeeded') 134 }) 135 }) 136}) 137 138test('retry after a conflict', t => { 139 const REV = '72-47f2986bfd8e8b55068b204588bbf484' 140 const manifest = { 141 name: 'libnpmpublish', 142 version: '1.0.0', 143 description: 'some stuff' 144 } 145 return mockTar({ 146 'package.json': JSON.stringify(manifest), 147 'index.js': 'console.log("hello world")' 148 }).then(tarData => { 149 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 150 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 151 const basePackument = { 152 name: 'libnpmpublish', 153 description: 'some stuff', 154 readme: '', 155 _id: 'libnpmpublish', 156 'dist-tags': {}, 157 versions: {}, 158 _attachments: {} 159 } 160 const currentPackument = cloneDeep(Object.assign({}, basePackument, { 161 time: { 162 modified: new Date().toISOString(), 163 created: new Date().toISOString(), 164 '1.0.1': new Date().toISOString() 165 }, 166 'dist-tags': { latest: '1.0.1' }, 167 maintainers: [{ name: 'zkat', email: 'idk@idk.tech' }], 168 versions: { 169 '1.0.1': { 170 _id: 'libnpmpublish@1.0.1', 171 _nodeVersion: process.versions.node, 172 _npmVersion: '6.9.0', 173 name: 'libnpmpublish', 174 version: '1.0.1', 175 description: 'some stuff', 176 dist: { 177 shasum, 178 integrity: integrity.toString(), 179 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.1.tgz` 180 } 181 } 182 }, 183 _attachments: { 184 'libnpmpublish-1.0.1.tgz': { 185 'content_type': 'application/octet-stream', 186 data: tarData.toString('base64'), 187 length: tarData.length 188 } 189 } 190 })) 191 const newPackument = cloneDeep(Object.assign({}, basePackument, { 192 'dist-tags': { latest: '1.0.0' }, 193 maintainers: [{ name: 'other', email: 'other@idk.tech' }], 194 versions: { 195 '1.0.0': { 196 _id: 'libnpmpublish@1.0.0', 197 _nodeVersion: process.versions.node, 198 _npmVersion: '6.9.0', 199 _npmUser: { 200 name: 'other', 201 email: 'other@idk.tech' 202 }, 203 name: 'libnpmpublish', 204 version: '1.0.0', 205 description: 'some stuff', 206 maintainers: [{ name: 'other', email: 'other@idk.tech' }], 207 dist: { 208 shasum, 209 integrity: integrity.toString(), 210 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 211 } 212 } 213 }, 214 _attachments: { 215 'libnpmpublish-1.0.0.tgz': { 216 'content_type': 'application/octet-stream', 217 data: tarData.toString('base64'), 218 length: tarData.length 219 } 220 } 221 })) 222 const mergedPackument = cloneDeep(Object.assign({}, basePackument, { 223 time: currentPackument.time, 224 'dist-tags': { latest: '1.0.0' }, 225 maintainers: currentPackument.maintainers, 226 versions: Object.assign({}, currentPackument.versions, newPackument.versions), 227 _attachments: Object.assign({}, currentPackument._attachments, newPackument._attachments) 228 })) 229 const srv = tnock(t, REG) 230 srv.put('/libnpmpublish', body => { 231 t.notOk(body._rev, 'no _rev in initial post') 232 t.deepEqual(body, newPackument, 'got conflicting packument') 233 return true 234 }).reply(409, { error: 'gimme _rev plz' }) 235 srv.get('/libnpmpublish?write=true').reply(200, Object.assign({ 236 _rev: REV 237 }, currentPackument)) 238 srv.put('/libnpmpublish', body => { 239 t.deepEqual(body, Object.assign({ 240 _rev: REV 241 }, mergedPackument), 'posted packument includes _rev and a merged version') 242 return true 243 }).reply(201, {}) 244 return publish(manifest, tarData, OPTS.concat({ 245 npmVersion: '6.9.0', 246 username: 'other', 247 email: 'other@idk.tech' 248 })).then(() => { 249 t.ok(true, 'publish succeeded') 250 }) 251 }) 252}) 253 254test('retry after a conflict -- no versions on remote', t => { 255 const REV = '72-47f2986bfd8e8b55068b204588bbf484' 256 const manifest = { 257 name: 'libnpmpublish', 258 version: '1.0.0', 259 description: 'some stuff' 260 } 261 return mockTar({ 262 'package.json': JSON.stringify(manifest), 263 'index.js': 'console.log("hello world")' 264 }).then(tarData => { 265 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 266 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 267 const basePackument = { 268 name: 'libnpmpublish', 269 description: 'some stuff', 270 readme: '', 271 _id: 'libnpmpublish' 272 } 273 const currentPackument = cloneDeep(Object.assign({}, basePackument, { 274 maintainers: [{ name: 'zkat', email: 'idk@idk.tech' }] 275 })) 276 const newPackument = cloneDeep(Object.assign({}, basePackument, { 277 'dist-tags': { latest: '1.0.0' }, 278 maintainers: [{ name: 'other', email: 'other@idk.tech' }], 279 versions: { 280 '1.0.0': { 281 _id: 'libnpmpublish@1.0.0', 282 _nodeVersion: process.versions.node, 283 _npmVersion: '6.9.0', 284 _npmUser: { 285 name: 'other', 286 email: 'other@idk.tech' 287 }, 288 name: 'libnpmpublish', 289 version: '1.0.0', 290 description: 'some stuff', 291 maintainers: [{ name: 'other', email: 'other@idk.tech' }], 292 dist: { 293 shasum, 294 integrity: integrity.toString(), 295 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 296 } 297 } 298 }, 299 _attachments: { 300 'libnpmpublish-1.0.0.tgz': { 301 'content_type': 'application/octet-stream', 302 data: tarData.toString('base64'), 303 length: tarData.length 304 } 305 } 306 })) 307 const mergedPackument = cloneDeep(Object.assign({}, basePackument, { 308 'dist-tags': { latest: '1.0.0' }, 309 maintainers: currentPackument.maintainers, 310 versions: Object.assign({}, currentPackument.versions, newPackument.versions), 311 _attachments: Object.assign({}, currentPackument._attachments, newPackument._attachments) 312 })) 313 const srv = tnock(t, REG) 314 srv.put('/libnpmpublish', body => { 315 t.notOk(body._rev, 'no _rev in initial post') 316 t.deepEqual(body, newPackument, 'got conflicting packument') 317 return true 318 }).reply(409, { error: 'gimme _rev plz' }) 319 srv.get('/libnpmpublish?write=true').reply(200, Object.assign({ 320 _rev: REV 321 }, currentPackument)) 322 srv.put('/libnpmpublish', body => { 323 t.deepEqual(body, Object.assign({ 324 _rev: REV 325 }, mergedPackument), 'posted packument includes _rev and a merged version') 326 return true 327 }).reply(201, {}) 328 return publish(manifest, tarData, OPTS.concat({ 329 npmVersion: '6.9.0', 330 username: 'other', 331 email: 'other@idk.tech' 332 })).then(() => { 333 t.ok(true, 'publish succeeded') 334 }) 335 }) 336}) 337test('version conflict', t => { 338 const REV = '72-47f2986bfd8e8b55068b204588bbf484' 339 const manifest = { 340 name: 'libnpmpublish', 341 version: '1.0.0', 342 description: 'some stuff' 343 } 344 return mockTar({ 345 'package.json': JSON.stringify(manifest), 346 'index.js': 'console.log("hello world")' 347 }).then(tarData => { 348 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 349 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 350 const basePackument = { 351 name: 'libnpmpublish', 352 description: 'some stuff', 353 readme: '', 354 _id: 'libnpmpublish', 355 'dist-tags': {}, 356 versions: {}, 357 _attachments: {} 358 } 359 const newPackument = cloneDeep(Object.assign({}, basePackument, { 360 'dist-tags': { latest: '1.0.0' }, 361 versions: { 362 '1.0.0': { 363 _id: 'libnpmpublish@1.0.0', 364 _nodeVersion: process.versions.node, 365 _npmVersion: '6.9.0', 366 name: 'libnpmpublish', 367 version: '1.0.0', 368 description: 'some stuff', 369 dist: { 370 shasum, 371 integrity: integrity.toString(), 372 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 373 } 374 } 375 }, 376 _attachments: { 377 'libnpmpublish-1.0.0.tgz': { 378 'content_type': 'application/octet-stream', 379 data: tarData.toString('base64'), 380 length: tarData.length 381 } 382 } 383 })) 384 const srv = tnock(t, REG) 385 srv.put('/libnpmpublish', body => { 386 t.notOk(body._rev, 'no _rev in initial post') 387 t.deepEqual(body, newPackument, 'got conflicting packument') 388 return true 389 }).reply(409, { error: 'gimme _rev plz' }) 390 srv.get('/libnpmpublish?write=true').reply(200, Object.assign({ 391 _rev: REV 392 }, newPackument)) 393 return publish(manifest, tarData, OPTS.concat({ 394 npmVersion: '6.9.0', 395 token: 'deadbeef' 396 })).then( 397 () => { throw new Error('should not succeed') }, 398 err => { 399 t.equal(err.code, 'EPUBLISHCONFLICT', 'got publish conflict code') 400 } 401 ) 402 }) 403}) 404 405test('publish with basic auth', t => { 406 const manifest = { 407 name: 'libnpmpublish', 408 version: '1.0.0', 409 description: 'some stuff' 410 } 411 return mockTar({ 412 'package.json': JSON.stringify(manifest), 413 'index.js': 'console.log("hello world")' 414 }).then(tarData => { 415 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 416 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 417 const packument = { 418 name: 'libnpmpublish', 419 description: 'some stuff', 420 readme: '', 421 _id: 'libnpmpublish', 422 'dist-tags': { 423 latest: '1.0.0' 424 }, 425 maintainers: [{ 426 name: 'zkat', 427 email: 'kat@example.tech' 428 }], 429 versions: { 430 '1.0.0': { 431 _id: 'libnpmpublish@1.0.0', 432 _nodeVersion: process.versions.node, 433 _npmVersion: '6.9.0', 434 _npmUser: { 435 name: 'zkat', 436 email: 'kat@example.tech' 437 }, 438 maintainers: [{ 439 name: 'zkat', 440 email: 'kat@example.tech' 441 }], 442 name: 'libnpmpublish', 443 version: '1.0.0', 444 description: 'some stuff', 445 dist: { 446 shasum, 447 integrity: integrity.toString(), 448 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 449 } 450 } 451 }, 452 _attachments: { 453 'libnpmpublish-1.0.0.tgz': { 454 'content_type': 'application/octet-stream', 455 data: tarData.toString('base64'), 456 length: tarData.length 457 } 458 } 459 } 460 const srv = tnock(t, REG) 461 srv.put('/libnpmpublish', body => { 462 t.deepEqual(body, packument, 'posted packument matches expectations') 463 return true 464 }, { 465 authorization: /^Basic / 466 }).reply(201, {}) 467 468 return publish(manifest, tarData, OPTS.concat({ 469 npmVersion: '6.9.0', 470 username: 'zkat', 471 email: 'kat@example.tech' 472 })).then(() => { 473 t.ok(true, 'publish succeeded') 474 }) 475 }) 476}) 477 478test('publish base64 string', t => { 479 const manifest = { 480 name: 'libnpmpublish', 481 version: '1.0.0', 482 description: 'some stuff' 483 } 484 return mockTar({ 485 'package.json': JSON.stringify(manifest), 486 'index.js': 'console.log("hello world")' 487 }).then(tarData => { 488 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 489 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 490 const packument = { 491 name: 'libnpmpublish', 492 description: 'some stuff', 493 readme: '', 494 _id: 'libnpmpublish', 495 'dist-tags': { 496 latest: '1.0.0' 497 }, 498 versions: { 499 '1.0.0': { 500 _id: 'libnpmpublish@1.0.0', 501 _nodeVersion: process.versions.node, 502 _npmVersion: '6.9.0', 503 name: 'libnpmpublish', 504 version: '1.0.0', 505 description: 'some stuff', 506 dist: { 507 shasum, 508 integrity: integrity.toString(), 509 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 510 } 511 } 512 }, 513 _attachments: { 514 'libnpmpublish-1.0.0.tgz': { 515 'content_type': 'application/octet-stream', 516 data: tarData.toString('base64'), 517 length: tarData.length 518 } 519 } 520 } 521 const srv = tnock(t, REG) 522 srv.put('/libnpmpublish', body => { 523 t.deepEqual(body, packument, 'posted packument matches expectations') 524 return true 525 }, { 526 authorization: 'Bearer deadbeef' 527 }).reply(201, {}) 528 529 return publish(manifest, tarData.toString('base64'), OPTS.concat({ 530 npmVersion: '6.9.0', 531 token: 'deadbeef' 532 })).then(() => { 533 t.ok(true, 'publish succeeded') 534 }) 535 }) 536}) 537 538test('publish tar stream', t => { 539 const manifest = { 540 name: 'libnpmpublish', 541 version: '1.0.0', 542 description: 'some stuff' 543 } 544 return mockTar({ 545 'package.json': JSON.stringify(manifest), 546 'index.js': 'console.log("hello world")' 547 }).then(tarData => { 548 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 549 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 550 const packument = { 551 name: 'libnpmpublish', 552 description: 'some stuff', 553 readme: '', 554 _id: 'libnpmpublish', 555 'dist-tags': { 556 latest: '1.0.0' 557 }, 558 versions: { 559 '1.0.0': { 560 _id: 'libnpmpublish@1.0.0', 561 _nodeVersion: process.versions.node, 562 _npmVersion: '6.9.0', 563 name: 'libnpmpublish', 564 version: '1.0.0', 565 description: 'some stuff', 566 dist: { 567 shasum, 568 integrity: integrity.toString(), 569 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 570 } 571 } 572 }, 573 _attachments: { 574 'libnpmpublish-1.0.0.tgz': { 575 'content_type': 'application/octet-stream', 576 data: tarData.toString('base64'), 577 length: tarData.length 578 } 579 } 580 } 581 const srv = tnock(t, REG) 582 srv.put('/libnpmpublish', body => { 583 t.deepEqual(body, packument, 'posted packument matches expectations') 584 return true 585 }, { 586 authorization: 'Bearer deadbeef' 587 }).reply(201, {}) 588 589 const stream = new PassThrough() 590 setTimeout(() => stream.end(tarData), 0) 591 return publish(manifest, stream, OPTS.concat({ 592 npmVersion: '6.9.0', 593 token: 'deadbeef' 594 })).then(() => { 595 t.ok(true, 'publish succeeded') 596 }) 597 }) 598}) 599 600test('refuse if package marked private', t => { 601 const manifest = { 602 name: 'libnpmpublish', 603 version: '1.0.0', 604 description: 'some stuff', 605 private: true 606 } 607 return mockTar({ 608 'package.json': JSON.stringify(manifest), 609 'index.js': 'console.log("hello world")' 610 }).then(tarData => { 611 return publish(manifest, tarData, OPTS.concat({ 612 npmVersion: '6.9.0', 613 token: 'deadbeef' 614 })).then( 615 () => { throw new Error('should not have succeeded') }, 616 err => { 617 t.equal(err.code, 'EPRIVATE', 'got correct error code') 618 } 619 ) 620 }) 621}) 622 623test('publish includes access', t => { 624 const manifest = { 625 name: 'libnpmpublish', 626 version: '1.0.0', 627 description: 'some stuff' 628 } 629 return mockTar({ 630 'package.json': JSON.stringify(manifest), 631 'index.js': 'console.log("hello world")' 632 }).then(tarData => { 633 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 634 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 635 const packument = { 636 name: 'libnpmpublish', 637 description: 'some stuff', 638 readme: '', 639 access: 'public', 640 _id: 'libnpmpublish', 641 'dist-tags': { 642 latest: '1.0.0' 643 }, 644 versions: { 645 '1.0.0': { 646 _id: 'libnpmpublish@1.0.0', 647 _nodeVersion: process.versions.node, 648 name: 'libnpmpublish', 649 version: '1.0.0', 650 description: 'some stuff', 651 dist: { 652 shasum, 653 integrity: integrity.toString(), 654 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 655 } 656 } 657 }, 658 _attachments: { 659 'libnpmpublish-1.0.0.tgz': { 660 'content_type': 'application/octet-stream', 661 data: tarData.toString('base64'), 662 length: tarData.length 663 } 664 } 665 } 666 const srv = tnock(t, REG) 667 srv.put('/libnpmpublish', body => { 668 t.deepEqual(body, packument, 'posted packument matches expectations') 669 return true 670 }, { 671 authorization: 'Bearer deadbeef' 672 }).reply(201, {}) 673 674 return publish(manifest, tarData, OPTS.concat({ 675 token: 'deadbeef', 676 access: 'public' 677 })).then(() => { 678 t.ok(true, 'publish succeeded') 679 }) 680 }) 681}) 682 683test('refuse if package is unscoped plus `restricted` access', t => { 684 const manifest = { 685 name: 'libnpmpublish', 686 version: '1.0.0', 687 description: 'some stuff' 688 } 689 return mockTar({ 690 'package.json': JSON.stringify(manifest), 691 'index.js': 'console.log("hello world")' 692 }).then(tarData => { 693 return publish(manifest, tarData, OPTS.concat({ 694 npmVersion: '6.9.0', 695 access: 'restricted' 696 })).then( 697 () => { throw new Error('should not have succeeded') }, 698 err => { 699 t.equal(err.code, 'EUNSCOPED', 'got correct error code') 700 } 701 ) 702 }) 703}) 704 705test('refuse if tarball is wrong type', t => { 706 const manifest = { 707 name: 'libnpmpublish', 708 version: '1.0.0', 709 description: 'some stuff' 710 } 711 return publish(manifest, { data: 42 }, OPTS.concat({ 712 npmVersion: '6.9.0', 713 token: 'deadbeef' 714 })).then( 715 () => { throw new Error('should not have succeeded') }, 716 err => { 717 t.equal(err.code, 'EBADTAR', 'got correct error code') 718 } 719 ) 720}) 721 722test('refuse if bad semver on manifest', t => { 723 const manifest = { 724 name: 'libnpmpublish', 725 version: 'lmao', 726 description: 'some stuff' 727 } 728 return publish(manifest, 'deadbeef', OPTS).then( 729 () => { throw new Error('should not have succeeded') }, 730 err => { 731 t.equal(err.code, 'EBADSEMVER', 'got correct error code') 732 } 733 ) 734}) 735 736test('other error code', t => { 737 const manifest = { 738 name: 'libnpmpublish', 739 version: '1.0.0', 740 description: 'some stuff' 741 } 742 return mockTar({ 743 'package.json': JSON.stringify(manifest), 744 'index.js': 'console.log("hello world")' 745 }).then(tarData => { 746 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 747 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 748 const packument = { 749 name: 'libnpmpublish', 750 description: 'some stuff', 751 readme: '', 752 _id: 'libnpmpublish', 753 'dist-tags': { 754 latest: '1.0.0' 755 }, 756 versions: { 757 '1.0.0': { 758 _id: 'libnpmpublish@1.0.0', 759 _nodeVersion: process.versions.node, 760 _npmVersion: '6.9.0', 761 name: 'libnpmpublish', 762 version: '1.0.0', 763 description: 'some stuff', 764 dist: { 765 shasum, 766 integrity: integrity.toString(), 767 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 768 } 769 } 770 }, 771 _attachments: { 772 'libnpmpublish-1.0.0.tgz': { 773 'content_type': 'application/octet-stream', 774 data: tarData.toString('base64'), 775 length: tarData.length 776 } 777 } 778 } 779 const srv = tnock(t, REG) 780 srv.put('/libnpmpublish', body => { 781 t.deepEqual(body, packument, 'posted packument matches expectations') 782 return true 783 }, { 784 authorization: 'Bearer deadbeef' 785 }).reply(500, { error: 'go away' }) 786 787 return publish(manifest, tarData, OPTS.concat({ 788 npmVersion: '6.9.0', 789 token: 'deadbeef' 790 })).then( 791 () => { throw new Error('should not succeed') }, 792 err => { 793 t.match(err.message, /go away/, 'no retry on non-409') 794 } 795 ) 796 }) 797}) 798 799test('publish includes access', t => { 800 const manifest = { 801 name: 'libnpmpublish', 802 version: '1.0.0', 803 description: 'some stuff' 804 } 805 return mockTar({ 806 'package.json': JSON.stringify(manifest), 807 'index.js': 'console.log("hello world")' 808 }).then(tarData => { 809 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 810 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 811 const packument = { 812 name: 'libnpmpublish', 813 description: 'some stuff', 814 readme: '', 815 access: 'public', 816 _id: 'libnpmpublish', 817 'dist-tags': { 818 latest: '1.0.0' 819 }, 820 versions: { 821 '1.0.0': { 822 _id: 'libnpmpublish@1.0.0', 823 _nodeVersion: process.versions.node, 824 name: 'libnpmpublish', 825 version: '1.0.0', 826 description: 'some stuff', 827 dist: { 828 shasum, 829 integrity: integrity.toString(), 830 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 831 } 832 } 833 }, 834 _attachments: { 835 'libnpmpublish-1.0.0.tgz': { 836 'content_type': 'application/octet-stream', 837 data: tarData.toString('base64'), 838 length: tarData.length 839 } 840 } 841 } 842 const srv = tnock(t, REG) 843 srv.put('/libnpmpublish', body => { 844 t.deepEqual(body, packument, 'posted packument matches expectations') 845 return true 846 }, { 847 authorization: 'Bearer deadbeef' 848 }).reply(201, {}) 849 850 return publish(manifest, tarData, OPTS.concat({ 851 token: 'deadbeef', 852 access: 'public' 853 })).then(() => { 854 t.ok(true, 'publish succeeded') 855 }) 856 }) 857}) 858 859test('publishConfig on manifest', t => { 860 const manifest = { 861 name: 'libnpmpublish', 862 version: '1.0.0', 863 description: 'some stuff', 864 publishConfig: { 865 registry: REG 866 } 867 } 868 return mockTar({ 869 'package.json': JSON.stringify(manifest), 870 'index.js': 'console.log("hello world")' 871 }).then(tarData => { 872 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 873 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 874 const packument = { 875 name: 'libnpmpublish', 876 description: 'some stuff', 877 readme: '', 878 _id: 'libnpmpublish', 879 'dist-tags': { 880 latest: '1.0.0' 881 }, 882 versions: { 883 '1.0.0': { 884 _id: 'libnpmpublish@1.0.0', 885 _nodeVersion: process.versions.node, 886 name: 'libnpmpublish', 887 version: '1.0.0', 888 description: 'some stuff', 889 dist: { 890 shasum, 891 integrity: integrity.toString(), 892 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 893 }, 894 publishConfig: { 895 registry: REG 896 } 897 } 898 }, 899 _attachments: { 900 'libnpmpublish-1.0.0.tgz': { 901 'content_type': 'application/octet-stream', 902 data: tarData.toString('base64'), 903 length: tarData.length 904 } 905 } 906 } 907 const srv = tnock(t, REG) 908 srv.put('/libnpmpublish', body => { 909 t.deepEqual(body, packument, 'posted packument matches expectations') 910 return true 911 }, { 912 authorization: 'Bearer deadbeef' 913 }).reply(201, {}) 914 915 return publish(manifest, tarData, { token: 'deadbeef' }).then(ret => { 916 t.ok(ret, 'publish succeeded') 917 }) 918 }) 919}) 920 921test('publish with encoded _auth', t => { 922 const manifest = { 923 name: 'libnpmpublish', 924 version: '1.0.0', 925 description: 'some stuff' 926 } 927 return mockTar({ 928 'package.json': JSON.stringify(manifest), 929 'index.js': 'console.log("hello world")' 930 }).then(tarData => { 931 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 932 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 933 const packument = { 934 name: 'libnpmpublish', 935 description: 'some stuff', 936 readme: '', 937 _id: 'libnpmpublish', 938 'dist-tags': { 939 latest: '1.0.0' 940 }, 941 maintainers: [ 942 { name: 'myuser', email: 'my@ema.il' } 943 ], 944 versions: { 945 '1.0.0': { 946 _id: 'libnpmpublish@1.0.0', 947 _npmUser: { 948 name: 'myuser', 949 email: 'my@ema.il' 950 }, 951 maintainers: [ 952 { name: 'myuser', email: 'my@ema.il' } 953 ], 954 _nodeVersion: process.versions.node, 955 name: 'libnpmpublish', 956 version: '1.0.0', 957 description: 'some stuff', 958 dist: { 959 shasum, 960 integrity: integrity.toString(), 961 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 962 } 963 } 964 }, 965 _attachments: { 966 'libnpmpublish-1.0.0.tgz': { 967 'content_type': 'application/octet-stream', 968 data: tarData.toString('base64'), 969 length: tarData.length 970 } 971 } 972 } 973 const srv = tnock(t, REG) 974 srv.put('/libnpmpublish', body => { 975 t.deepEqual(body, packument, 'posted packument matches expectations') 976 return true 977 }, { 978 authorization: 'Bearer deadbeef' 979 }).reply(201, {}) 980 981 return publish(manifest, tarData, OPTS.concat({ 982 _auth: Buffer.from('myuser:mypassword', 'utf8').toString('base64'), 983 email: 'my@ema.il' 984 })).then(ret => { 985 t.ok(ret, 'publish succeeded using _auth') 986 }) 987 }) 988}) 989 990test('publish with 302 redirect', t => { 991 const manifest = { 992 name: 'libnpmpublish', 993 version: '1.0.0', 994 description: 'some stuff' 995 } 996 return mockTar({ 997 'package.json': JSON.stringify(manifest), 998 'index.js': 'console.log("hello world")' 999 }).then(tarData => { 1000 const shasum = crypto.createHash('sha1').update(tarData).digest('hex') 1001 const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] }) 1002 const packument = { 1003 name: 'libnpmpublish', 1004 description: 'some stuff', 1005 readme: '', 1006 _id: 'libnpmpublish', 1007 'dist-tags': { 1008 latest: '1.0.0' 1009 }, 1010 versions: { 1011 '1.0.0': { 1012 _id: 'libnpmpublish@1.0.0', 1013 _nodeVersion: process.versions.node, 1014 name: 'libnpmpublish', 1015 version: '1.0.0', 1016 description: 'some stuff', 1017 dist: { 1018 shasum, 1019 integrity: integrity.toString(), 1020 tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz` 1021 } 1022 } 1023 }, 1024 _attachments: { 1025 'libnpmpublish-1.0.0.tgz': { 1026 'content_type': 'application/octet-stream', 1027 data: tarData.toString('base64'), 1028 length: tarData.length 1029 } 1030 } 1031 } 1032 tnock(t, REG).put('/libnpmpublish').reply(302, '', { 1033 location: 'http://blah.net/libnpmpublish' 1034 }) 1035 tnock(t, 'http://blah.net').put('/libnpmpublish', body => { 1036 t.deepEqual(body, packument, 'posted packument matches expectations') 1037 return true 1038 }, { 1039 authorization: 'Bearer deadbeef' 1040 }).reply(201, {}) 1041 1042 return publish(manifest, tarData, OPTS.concat({ 1043 token: 'deadbeef' 1044 })).then(ret => { 1045 t.ok(ret, 'publish succeeded') 1046 }) 1047 }) 1048}) 1049