@@ -6,6 +6,7 @@ const runScript = require('@npmcli/run-script')
6
6
const fs = require ( 'fs' )
7
7
const readdir = util . promisify ( fs . readdir )
8
8
const log = require ( '../utils/log-shim.js' )
9
+ const validateLockfile = require ( '../utils/validate-lockfile.js' )
9
10
10
11
const removeNodeModules = async where => {
11
12
const rimrafOpts = { glob : false }
@@ -37,6 +38,7 @@ class CI extends ArboristWorkspaceCmd {
37
38
const where = this . npm . prefix
38
39
const opts = {
39
40
...this . npm . flatOptions ,
41
+ packageLock : true , // npm ci should never skip lock files
40
42
path : where ,
41
43
log,
42
44
save : false , // npm ci should never modify the lockfile or package.json
@@ -55,6 +57,28 @@ class CI extends ArboristWorkspaceCmd {
55
57
} ) ,
56
58
removeNodeModules ( where ) ,
57
59
] )
60
+
61
+ // retrieves inventory of packages from loaded virtual tree (lock file)
62
+ const virtualInventory = new Map ( arb . virtualTree . inventory )
63
+
64
+ // build ideal tree step needs to come right after retrieving the virtual
65
+ // inventory since it's going to erase the previous ref to virtualTree
66
+ await arb . buildIdealTree ( )
67
+
68
+ // verifies that the packages from the ideal tree will match
69
+ // the same versions that are present in the virtual tree (lock file)
70
+ // throws a validation error in case of mismatches
71
+ const errors = validateLockfile ( virtualInventory , arb . idealTree . inventory )
72
+ if ( errors . length ) {
73
+ throw new Error (
74
+ '`npm ci` can only install packages when your package.json and ' +
75
+ 'package-lock.json or npm-shrinkwrap.json are in sync. Please ' +
76
+ 'update your lock file with `npm install` ' +
77
+ 'before continuing.\n\n' +
78
+ errors . join ( '\n' ) + '\n'
79
+ )
80
+ }
81
+
58
82
await arb . reify ( opts )
59
83
60
84
const ignoreScripts = this . npm . config . get ( 'ignore-scripts' )
0 commit comments