diff options
-rw-r--r-- | lib/install/read-shrinkwrap.js | 26 | ||||
-rw-r--r-- | lib/shrinkwrap.js | 59 | ||||
-rw-r--r-- | package-lock.json | 3 | ||||
-rw-r--r-- | test/tap/dedupe.js | 1 | ||||
-rw-r--r-- | test/tap/shrinkwrap-default-dev.js | 2 | ||||
-rw-r--r-- | test/tap/shrinkwrap-dev-dependency.js | 2 | ||||
-rw-r--r-- | test/tap/shrinkwrap-empty-deps.js | 34 | ||||
-rw-r--r-- | test/tap/shrinkwrap-extra-metadata.js | 83 | ||||
-rw-r--r-- | test/tap/shrinkwrap-local-dependency.js | 6 | ||||
-rw-r--r-- | test/tap/shrinkwrap-optional-dependency.js | 2 | ||||
-rw-r--r-- | test/tap/shrinkwrap-optional-property.js | 2 | ||||
-rw-r--r-- | test/tap/shrinkwrap-prod-dependency-also.js | 6 | ||||
-rw-r--r-- | test/tap/shrinkwrap-prod-dependency.js | 2 | ||||
-rw-r--r-- | test/tap/shrinkwrap-shared-dev-dependency.js | 2 |
14 files changed, 194 insertions, 36 deletions
diff --git a/lib/install/read-shrinkwrap.js b/lib/install/read-shrinkwrap.js index bf941a49c..b18265bd4 100644 --- a/lib/install/read-shrinkwrap.js +++ b/lib/install/read-shrinkwrap.js @@ -8,6 +8,7 @@ const inflateShrinkwrap = require('./inflate-shrinkwrap.js') const log = require('npmlog') const parseJSON = require('../utils/parse-json.js') const path = require('path') +const ssri = require('ssri') const readFileAsync = BB.promisify(fs.readFile) @@ -15,28 +16,37 @@ module.exports = readShrinkwrap function readShrinkwrap (child, next) { if (child.package._shrinkwrap) return process.nextTick(next) BB.join( - readLockfile('npm-shrinkwrap.json', child), + maybeReadFile('npm-shrinkwrap.json', child), // Don't read non-root lockfiles - child.isTop && readLockfile('package-lock.json', child), - (shrinkwrap, lockfile) => { + child.isTop && maybeReadFile('package-lock.json', child), + child.isTop && maybeReadFile('package.json', child), + (shrinkwrap, lockfile, pkgJson) => { if (shrinkwrap && lockfile) { log.warn('read-shrinkwrap', 'Ignoring package-lock.json because there is already an npm-shrinkwrap.json. Please use only one of the two.') } + let parsed = null if (shrinkwrap || lockfile) { try { - child.package._shrinkwrap = parseJSON(shrinkwrap || lockfile) + parsed = parseJSON(shrinkwrap || lockfile) } catch (ex) { - child.package._shrinkwrap = null throw ex } - } else { - child.package._shrinkwrap = null } + if ( + pkgJson && + parsed && + parsed.packageIntegrity && + !ssri.checkData(pkgJson, parsed.packageIntegrity) + ) { + let name = shrinkwrap ? 'npm-shrinkwrap.json' : 'package-lock.json' + log.info('read-shrinkwrap', `${name} will be updated because package.json does not match what it was generated against.`) + } + child.package._shrinkwrap = parsed } ).then(() => next(), next) } -function readLockfile (name, child) { +function maybeReadFile (name, child) { return readFileAsync( path.join(child.path, name) ).catch({code: 'ENOENT'}, () => null) diff --git a/lib/shrinkwrap.js b/lib/shrinkwrap.js index 832479e73..c764d3837 100644 --- a/lib/shrinkwrap.js +++ b/lib/shrinkwrap.js @@ -26,12 +26,14 @@ const writeFileAtomic = require('write-file-atomic') const PKGLOCK = 'package-lock.json' const SHRINKWRAP = 'npm-shrinkwrap.json' +const PKGLOCK_VERSION = 1 // emit JSON describing versions of all packages currently installed (for later // use with shrinkwrap install) shrinkwrap.usage = 'npm shrinkwrap' module.exports = exports = shrinkwrap +module.exports.PKGLOCK_VERSION = PKGLOCK_VERSION function shrinkwrap (args, silent, cb) { if (typeof cb !== 'function') { cb = silent @@ -74,8 +76,7 @@ module.exports.createShrinkwrap = createShrinkwrap function createShrinkwrap (tree, opts, cb) { opts = opts || {} lifecycle(tree.package, 'preshrinkwrap', tree.path, function () { - var pkginfo = treeToShrinkwrap(tree) - + const pkginfo = treeToShrinkwrap(tree) chain([ [lifecycle, tree.package, 'shrinkwrap', tree.path], [shrinkwrap_, tree.path, pkginfo, opts], @@ -182,14 +183,19 @@ function save (dir, pkginfo, opts, cb) { const info = ( shrinkwrap || lockfile || - [path.resolve(dir, opts.defaultFile || PKGLOCK), (pkg && pkg[1]) || 2] + { + path: path.resolve(dir, opts.defaultFile || PKGLOCK), + data: '{}', + indent: (pkg && pkg.indent) || 2 + } ) - const swdata = JSON.stringify(pkginfo, null, info[1]) + '\n' - writeFileAtomic(info[0], swdata, (err) => { + const updated = updateLockfileMetadata(pkginfo, pkg && pkg.data) + const swdata = JSON.stringify(updated, null, info.indent) + '\n' + writeFileAtomic(info.path, swdata, (err) => { if (err) return cb(err) if (opts.silent) return cb(null, pkginfo) if (!shrinkwrap && !lockfile) { - log.notice('', `created a lockfile as ${path.basename(info[0])}. You should commit this file.`) + log.notice('', `created a lockfile as ${path.basename(info.path)}. You should commit this file.`) } cb(null, pkginfo) }) @@ -198,12 +204,51 @@ function save (dir, pkginfo, opts, cb) { }, cb) } +function updateLockfileMetadata (pkginfo, pkgJson) { + // This is a lot of work just to make sure the extra metadata fields are + // between version and dependencies fields, without affecting any other stuff + const newPkg = {} + let metainfoWritten = false + const metainfo = new Set([ + 'createdWith', + 'lockfileVersion', + 'packageIntegrity' + ]) + Object.keys(pkginfo).forEach((k) => { + if (k === 'dependencies') { + writeMetainfo(newPkg) + } + if (!metainfo.has(k)) { + newPkg[k] = pkginfo[k] + } + if (k === 'version') { + writeMetainfo(newPkg) + } + }) + if (!metainfoWritten) { + writeMetainfo(newPkg) + } + function writeMetainfo (pkginfo) { + pkginfo.createdWith = `npm@${npm.version}` + pkginfo.lockfileVersion = PKGLOCK_VERSION + pkginfo.packageIntegrity = pkgJson && ssri.fromData(pkgJson, { + algorithms: ['sha512'] + }).toString() + metainfoWritten = true + } + return newPkg +} + function checkPackageFile (dir, name) { const file = path.resolve(dir, name) return fs.readFileAsync( file, 'utf8' ).then((data) => { - return [file, detectIndent(data).indent || ' '] + return { + path: file, + data, + indent: detectIndent(data).indent || 2 + } }).catch({code: 'ENOENT'}, () => {}) } diff --git a/package-lock.json b/package-lock.json index b4365b614..f6764e305 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,9 @@ { "name": "npm", "version": "4.6.1", + "createdWith": "npm@4.6.1", + "lockfileVersion": 1, + "packageIntegrity": "sha512-DS0oU+EJLlcc0fmN5pb8OwuG8h/JYKIEIoJBIWdykZk/i0HjXW1dChjcMwUUEFAAIIWH5Ru27RDPnVqWTvApCQ==", "dependencies": { "abbrev": { "version": "1.1.0", diff --git a/test/tap/dedupe.js b/test/tap/dedupe.js index bdda3474a..b6c601cba 100644 --- a/test/tap/dedupe.js +++ b/test/tap/dedupe.js @@ -60,6 +60,7 @@ test('setup', function (t) { test('dedupe finds the common module and moves it up one level', function (t) { common.npm([ '--registry', common.registry, + '--no-save', 'install', '.' ], EXEC_OPTS, diff --git a/test/tap/shrinkwrap-default-dev.js b/test/tap/shrinkwrap-default-dev.js index 67266644c..5c8929a43 100644 --- a/test/tap/shrinkwrap-default-dev.js +++ b/test/tap/shrinkwrap-default-dev.js @@ -83,7 +83,7 @@ test('shrinkwrap-default-dev', function (t) { t.comment(stdout.trim()) t.comment(stderr.trim()) var swrap = JSON.parse(fs.readFileSync(shrinkwrapPath)) - t.isDeeply(swrap, shrinkwrapWithDev, 'Shrinkwrap included dev deps by default') + t.isDeeply(swrap.dependencies, shrinkwrapWithDev.dependencies, 'Shrinkwrap included dev deps by default') t.done() }) }) diff --git a/test/tap/shrinkwrap-dev-dependency.js b/test/tap/shrinkwrap-dev-dependency.js index 20e2de1d2..79d3b7cd0 100644 --- a/test/tap/shrinkwrap-dev-dependency.js +++ b/test/tap/shrinkwrap-dev-dependency.js @@ -79,7 +79,7 @@ test("shrinkwrap doesn't strip out the dependency", function (t) { } catch (ex) { t.comment(ex) } - t.deepEqual(results, desired) + t.deepEqual(results.dependencies, desired.dependencies) s.close() t.end() }) diff --git a/test/tap/shrinkwrap-empty-deps.js b/test/tap/shrinkwrap-empty-deps.js index cf7e9a6e8..37ce4d4f1 100644 --- a/test/tap/shrinkwrap-empty-deps.js +++ b/test/tap/shrinkwrap-empty-deps.js @@ -1,19 +1,22 @@ -var fs = require('fs') -var path = require('path') +'use strict' -var mkdirp = require('mkdirp') -var mr = require('npm-registry-mock') -var osenv = require('osenv') -var rimraf = require('rimraf') -var test = require('tap').test +const common = require('../common-tap.js') +const fs = require('fs') +const mkdirp = require('mkdirp') +const mr = require('npm-registry-mock') +const npm = require('../../lib/npm.js') +const osenv = require('osenv') +const path = require('path') +const rimraf = require('rimraf') +const shrinkwrap = require('../../lib/shrinkwrap.js') +const ssri = require('ssri') +const test = require('tap').test -var common = require('../common-tap.js') +const pkg = path.resolve(__dirname, 'shrinkwrap-empty-deps') -var pkg = path.resolve(__dirname, 'shrinkwrap-empty-deps') +const EXEC_OPTS = { cwd: pkg } -var EXEC_OPTS = { cwd: pkg } - -var json = { +const json = { author: 'Rockbert', name: 'shrinkwrap-empty-deps', version: '0.0.0', @@ -51,7 +54,12 @@ test('returns a list of removed items', function (t) { t.same( { 'name': 'shrinkwrap-empty-deps', - 'version': '0.0.0' + 'version': '0.0.0', + 'createdWith': `npm@${npm.version}`, + 'lockfileVersion': shrinkwrap.PKGLOCK_VERSION, + 'packageIntegrity': ssri.fromData(JSON.stringify(json, null, 2), { + algorithms: ['sha512'] + }).toString() }, JSON.parse(desired), 'shrinkwrap handled empty deps without exploding' diff --git a/test/tap/shrinkwrap-extra-metadata.js b/test/tap/shrinkwrap-extra-metadata.js new file mode 100644 index 000000000..1947ee575 --- /dev/null +++ b/test/tap/shrinkwrap-extra-metadata.js @@ -0,0 +1,83 @@ +'use strict' + +const common = require('../common-tap.js') +const fs = require('fs') +const mkdirp = require('mkdirp') +const mr = require('npm-registry-mock') +const npm = require('../../lib/npm.js') +const osenv = require('osenv') +const path = require('path') +const rimraf = require('rimraf') +const shrinkwrap = require('../../lib/shrinkwrap.js') +const ssri = require('ssri') +const test = require('tap').test + +const pkg = path.join(__dirname, path.basename(__filename, '.js')) + +const EXEC_OPTS = { cwd: pkg } + +const json = { + author: 'Rockbert', + name: 'shrinkwrap-extra-metadata', + version: '0.0.0' +} + +test('setup', function (t) { + cleanup() + mkdirp.sync(pkg) + fs.writeFileSync( + path.join(pkg, 'package.json'), + JSON.stringify(json, null, 2) + ) + + process.chdir(pkg) + t.end() +}) + +test('adds additional metadata fields from the pkglock spec', function (t) { + mr({ port: common.port }, function (er, s) { + common.npm( + [ + '--registry', common.registry, + '--loglevel', 'silent', + 'shrinkwrap' + ], + EXEC_OPTS, + function (err, code, stdout, stderr) { + t.ifError(err, 'shrinkwrap ran without issue') + t.notOk(code, 'shrinkwrap ran without raising error code') + + fs.readFile(path.resolve(pkg, 'npm-shrinkwrap.json'), function (err, desired) { + t.ifError(err, 'read npm-shrinkwrap.json without issue') + t.same( + { + 'name': 'shrinkwrap-extra-metadata', + 'version': '0.0.0', + 'createdWith': `npm@${npm.version}`, + 'lockfileVersion': shrinkwrap.PKGLOCK_VERSION, + 'packageIntegrity': ssri.fromData(JSON.stringify(json, null, 2), { + algorithms: ['sha512'] + }).toString() + }, + JSON.parse(desired), + 'shrinkwrap wrote the expected metadata fields' + ) + + s.close() + t.end() + }) + } + ) + }) +}) + +test('cleanup', function (t) { + cleanup() + + t.end() +}) + +function cleanup () { + process.chdir(osenv.tmpdir()) + rimraf.sync(pkg) +} diff --git a/test/tap/shrinkwrap-local-dependency.js b/test/tap/shrinkwrap-local-dependency.js index 56e044de0..992343ccf 100644 --- a/test/tap/shrinkwrap-local-dependency.js +++ b/test/tap/shrinkwrap-local-dependency.js @@ -85,7 +85,11 @@ test('shrinkwrap uses resolved with file: on local deps', function (t) { t.comment(stderr.trim()) t.equal(code, 0, 'npm exited normally') var data = fs.readFileSync(path.join(testdir, 'npm-shrinkwrap.json'), { encoding: 'utf8' }) - t.deepEqual(JSON.parse(data), shrinkwrap, 'shrinkwrap looks correct') + t.deepEqual( + JSON.parse(data).dependencies, + shrinkwrap.dependencies, + 'shrinkwrap looks correct' + ) t.end() }) }) diff --git a/test/tap/shrinkwrap-optional-dependency.js b/test/tap/shrinkwrap-optional-dependency.js index 0e9b3b202..0373e89e6 100644 --- a/test/tap/shrinkwrap-optional-dependency.js +++ b/test/tap/shrinkwrap-optional-dependency.js @@ -43,7 +43,7 @@ test('shrinkwrap does not fail on missing optional dependency', function (t) { npm.commands.shrinkwrap([], true, function (err, results) { if (err) return fail(err) - t.deepEqual(results, desired) + t.deepEqual(results.dependencies, desired.dependencies) s.close() t.end() }) diff --git a/test/tap/shrinkwrap-optional-property.js b/test/tap/shrinkwrap-optional-property.js index 52deba863..19e55a45f 100644 --- a/test/tap/shrinkwrap-optional-property.js +++ b/test/tap/shrinkwrap-optional-property.js @@ -33,7 +33,7 @@ test('shrinkwrap adds optional property when optional dependency', function (t) npm.commands.shrinkwrap([], true, function (err, results) { if (err) return fail(err) - t.deepEqual(results, desired) + t.deepEqual(results.dependencies, desired.dependencies) s.close() t.end() }) diff --git a/test/tap/shrinkwrap-prod-dependency-also.js b/test/tap/shrinkwrap-prod-dependency-also.js index 4d18aedca..6cc388946 100644 --- a/test/tap/shrinkwrap-prod-dependency-also.js +++ b/test/tap/shrinkwrap-prod-dependency-also.js @@ -46,7 +46,11 @@ test("shrinkwrap --also=development doesn't strip out prod dependencies", functi t.ifError(ex, 'read shrinkwrap') } } - t.deepEqual(results, desired, 'results have dev dep') + t.deepEqual( + results.dependencies, + desired.dependencies, + 'results have dev dep' + ) s.done() t.end() }) diff --git a/test/tap/shrinkwrap-prod-dependency.js b/test/tap/shrinkwrap-prod-dependency.js index cdb6416cc..023a3bf0b 100644 --- a/test/tap/shrinkwrap-prod-dependency.js +++ b/test/tap/shrinkwrap-prod-dependency.js @@ -26,7 +26,7 @@ test("shrinkwrap --dev doesn't strip out prod dependencies", function (t) { npm.commands.shrinkwrap([], true, function (err, results) { if (err) return t.fail(err) - t.deepEqual(results, desired) + t.deepEqual(results.dependencies, desired.dependencies) s.close() t.end() }) diff --git a/test/tap/shrinkwrap-shared-dev-dependency.js b/test/tap/shrinkwrap-shared-dev-dependency.js index 0414ac8c2..71e6371bc 100644 --- a/test/tap/shrinkwrap-shared-dev-dependency.js +++ b/test/tap/shrinkwrap-shared-dev-dependency.js @@ -67,7 +67,7 @@ test("shrinkwrap doesn't strip out the shared dependency", function (t) { }).spread((code) => { t.is(code, 0, 'shrinkwrap') var results = JSON.parse(fs.readFileSync(`${pkg}/npm-shrinkwrap.json`)) - t.deepEqual(results, desired) + t.deepEqual(results.dependencies, desired.dependencies) t.end() }) }) |