@npmcli/arborist 4.2.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,7 +41,7 @@ const _complete = Symbol('complete')
41
41
  const _depsSeen = Symbol('depsSeen')
42
42
  const _depsQueue = Symbol('depsQueue')
43
43
  const _currentDep = Symbol('currentDep')
44
- const _updateAll = Symbol('updateAll')
44
+ const _updateAll = Symbol.for('updateAll')
45
45
  const _mutateTree = Symbol('mutateTree')
46
46
  const _flagsSuspect = Symbol.for('flagsSuspect')
47
47
  const _workspaces = Symbol.for('workspaces')
@@ -176,7 +176,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
176
176
  // public method
177
177
  async buildIdealTree (options = {}) {
178
178
  if (this.idealTree) {
179
- return Promise.resolve(this.idealTree)
179
+ return this.idealTree
180
180
  }
181
181
 
182
182
  // allow the user to set reify options on the ctor as well.
@@ -194,8 +194,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
194
194
  process.emit('time', 'idealTree')
195
195
 
196
196
  if (!options.add && !options.rm && !options.update && this[_global]) {
197
- const er = new Error('global requires add, rm, or update option')
198
- return Promise.reject(er)
197
+ throw new Error('global requires add, rm, or update option')
199
198
  }
200
199
 
201
200
  // first get the virtual tree, if possible. If there's a lockfile, then
@@ -334,6 +333,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
334
333
  root.meta.lockfileVersion = defaultLockfileVersion
335
334
  }
336
335
  }
336
+ root.meta.inferFormattingOptions(root.package)
337
337
  return root
338
338
  })
339
339
 
@@ -1180,6 +1180,11 @@ This is a one-time fix-up, please be patient...
1180
1180
  return true
1181
1181
  }
1182
1182
 
1183
+ // If the edge is a workspace, and it's valid, leave it alone
1184
+ if (edge.to.isWorkspace) {
1185
+ return false
1186
+ }
1187
+
1183
1188
  // user explicitly asked to update this package by name, problem
1184
1189
  if (this[_updateNames].includes(edge.name)) {
1185
1190
  return true
@@ -58,6 +58,8 @@ const _bundleUnpacked = Symbol('bundleUnpacked')
58
58
  const _bundleMissing = Symbol('bundleMissing')
59
59
  const _reifyNode = Symbol.for('reifyNode')
60
60
  const _extractOrLink = Symbol('extractOrLink')
61
+ const _updateAll = Symbol.for('updateAll')
62
+ const _updateNames = Symbol.for('updateNames')
61
63
  // defined by rebuild mixin
62
64
  const _checkBins = Symbol.for('checkBins')
63
65
  const _symlink = Symbol('symlink')
@@ -1140,21 +1142,33 @@ module.exports = cls => class Reifier extends cls {
1140
1142
  // for install failures. Those still end up in the shrinkwrap, so we
1141
1143
  // save it first, then prune out the optional trash, and then return it.
1142
1144
 
1143
- // support save=false option
1144
- if (options.save === false || this[_global] || this[_dryRun]) {
1145
+ const save = !(options.save === false)
1146
+
1147
+ // we check for updates in order to make sure we run save ideal tree
1148
+ // even though save=false since we want `npm update` to be able to
1149
+ // write to package-lock files by default
1150
+ const hasUpdates = this[_updateAll] || this[_updateNames].length
1151
+
1152
+ // we're going to completely skip save ideal tree in case of a global or
1153
+ // dry-run install and also if the save option is set to false, EXCEPT for
1154
+ // update since the expected behavior for npm7+ is for update to
1155
+ // NOT save to package.json, we make that exception since we still want
1156
+ // saveIdealTree to be able to write the lockfile by default.
1157
+ const saveIdealTree = !(
1158
+ (!save && !hasUpdates)
1159
+ || this[_global]
1160
+ || this[_dryRun]
1161
+ )
1162
+
1163
+ if (!saveIdealTree) {
1145
1164
  return false
1146
1165
  }
1147
1166
 
1148
1167
  process.emit('time', 'reify:save')
1149
1168
 
1150
1169
  const updatedTrees = new Set()
1151
-
1152
- // resolvedAdd is the list of user add requests, but with names added
1153
- // to things like git repos and tarball file/urls. However, if the
1154
- // user requested 'foo@', and we have a foo@file:../foo, then we should
1155
- // end up saving the spec we actually used, not whatever they gave us.
1156
- if (this[_resolvedAdd].length) {
1157
- for (const { name, tree: addTree } of this[_resolvedAdd]) {
1170
+ const updateNodes = nodes => {
1171
+ for (const { name, tree: addTree } of nodes) {
1158
1172
  // addTree either the root, or a workspace
1159
1173
  const edge = addTree.edgesOut.get(name)
1160
1174
  const pkg = addTree.package
@@ -1168,7 +1182,7 @@ module.exports = cls => class Reifier extends cls {
1168
1182
  // that we couldn't resolve, this MAY be missing. if we haven't
1169
1183
  // blown up by now, it's because it was not a problem, though, so
1170
1184
  // just move on.
1171
- if (!child) {
1185
+ if (!child || !addTree.isTop) {
1172
1186
  continue
1173
1187
  }
1174
1188
 
@@ -1259,6 +1273,63 @@ module.exports = cls => class Reifier extends cls {
1259
1273
  }
1260
1274
  }
1261
1275
 
1276
+ // helper that retrieves an array of nodes that were
1277
+ // potentially updated during the reify process, in order
1278
+ // to limit the number of nodes to check and update, only
1279
+ // select nodes from the inventory that are direct deps
1280
+ // of a given package.json (project root or a workspace)
1281
+ // and in ase of using a list of `names`, restrict nodes
1282
+ // to only names that are found in this list
1283
+ const retrieveUpdatedNodes = names => {
1284
+ const filterDirectDependencies = node =>
1285
+ !node.isRoot && node.resolveParent.isRoot
1286
+ && (!names || names.includes(node.name))
1287
+ const directDeps = this.idealTree.inventory
1288
+ .filter(filterDirectDependencies)
1289
+
1290
+ // traverses the list of direct dependencies and collect all nodes
1291
+ // to be updated, since any of them might have changed during reify
1292
+ const nodes = []
1293
+ for (const node of directDeps) {
1294
+ for (const edgeIn of node.edgesIn) {
1295
+ nodes.push({
1296
+ name: node.name,
1297
+ tree: edgeIn.from.target,
1298
+ })
1299
+ }
1300
+ }
1301
+ return nodes
1302
+ }
1303
+
1304
+ if (save) {
1305
+ // when using update all alongside with save, we'll make
1306
+ // sure to refresh every dependency of the root idealTree
1307
+ if (this[_updateAll]) {
1308
+ const nodes = retrieveUpdatedNodes()
1309
+ updateNodes(nodes)
1310
+ } else {
1311
+ // resolvedAdd is the list of user add requests, but with names added
1312
+ // to things like git repos and tarball file/urls. However, if the
1313
+ // user requested 'foo@', and we have a foo@file:../foo, then we should
1314
+ // end up saving the spec we actually used, not whatever they gave us.
1315
+ if (this[_resolvedAdd].length) {
1316
+ updateNodes(this[_resolvedAdd])
1317
+ }
1318
+
1319
+ // if updating given dependencies by name, restrict the list of
1320
+ // nodes to check to only those currently in _updateNames
1321
+ if (this[_updateNames].length) {
1322
+ const nodes = retrieveUpdatedNodes(this[_updateNames])
1323
+ updateNodes(nodes)
1324
+ }
1325
+
1326
+ // grab any from explicitRequests that had deps removed
1327
+ for (const { from: tree } of this.explicitRequests) {
1328
+ updatedTrees.add(tree)
1329
+ }
1330
+ }
1331
+ }
1332
+
1262
1333
  // preserve indentation, if possible
1263
1334
  const {
1264
1335
  [Symbol.for('indent')]: indent,
@@ -1291,15 +1362,12 @@ module.exports = cls => class Reifier extends cls {
1291
1362
  await pkgJson.save()
1292
1363
  }
1293
1364
 
1294
- // grab any from explicitRequests that had deps removed
1295
- for (const { from: tree } of this.explicitRequests) {
1296
- updatedTrees.add(tree)
1297
- }
1298
-
1299
- for (const tree of updatedTrees) {
1300
- // refresh the edges so they have the correct specs
1301
- tree.package = tree.package
1302
- promises.push(updatePackageJson(tree))
1365
+ if (save) {
1366
+ for (const tree of updatedTrees) {
1367
+ // refresh the edges so they have the correct specs
1368
+ tree.package = tree.package
1369
+ promises.push(updatePackageJson(tree))
1370
+ }
1303
1371
  }
1304
1372
 
1305
1373
  await Promise.all(promises)
package/lib/shrinkwrap.js CHANGED
@@ -424,6 +424,18 @@ class Shrinkwrap {
424
424
  .map(fn => fn && maybeStatFile(fn)))
425
425
  }
426
426
 
427
+ inferFormattingOptions (packageJSONData) {
428
+ // don't use detect-indent, just pick the first line.
429
+ // if the file starts with {" then we have an indent of '', ie, none
430
+ // which will default to 2 at save time.
431
+ const {
432
+ [Symbol.for('indent')]: indent,
433
+ [Symbol.for('newline')]: newline,
434
+ } = packageJSONData
435
+ this.indent = indent !== undefined ? indent : this.indent
436
+ this.newline = newline !== undefined ? newline : this.newline
437
+ }
438
+
427
439
  load () {
428
440
  // we don't need to load package-lock.json except for top of tree nodes,
429
441
  // only npm-shrinkwrap.json.
@@ -451,15 +463,7 @@ class Shrinkwrap {
451
463
 
452
464
  return data ? parseJSON(data) : {}
453
465
  }).then(async data => {
454
- // don't use detect-indent, just pick the first line.
455
- // if the file starts with {" then we have an indent of '', ie, none
456
- // which will default to 2 at save time.
457
- const {
458
- [Symbol.for('indent')]: indent,
459
- [Symbol.for('newline')]: newline,
460
- } = data
461
- this.indent = indent !== undefined ? indent : this.indent
462
- this.newline = newline !== undefined ? newline : this.newline
466
+ this.inferFormattingOptions(data)
463
467
 
464
468
  if (!this.hiddenLockfile || !data.packages) {
465
469
  return data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@isaacs/string-locale-compare": "^1.1.0",
@@ -12,7 +12,7 @@
12
12
  "@npmcli/node-gyp": "^1.0.3",
13
13
  "@npmcli/package-json": "^1.0.1",
14
14
  "@npmcli/run-script": "^2.0.0",
15
- "bin-links": "^2.3.0",
15
+ "bin-links": "^3.0.0",
16
16
  "cacache": "^15.0.3",
17
17
  "common-ancestor-path": "^1.0.1",
18
18
  "json-parse-even-better-errors": "^2.3.1",
@@ -37,7 +37,7 @@
37
37
  "walk-up-path": "^1.0.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@npmcli/template-oss": "^2.3.1",
40
+ "@npmcli/template-oss": "^2.4.2",
41
41
  "benchmark": "^2.1.4",
42
42
  "chalk": "^4.1.0",
43
43
  "minify-registry-metadata": "^2.1.0",
@@ -94,9 +94,11 @@
94
94
  "engines": {
95
95
  "node": "^12.13.0 || ^14.15.0 || >=16"
96
96
  },
97
- "templateVersion": "2.3.1",
98
97
  "eslintIgnore": [
99
98
  "test/fixtures/",
100
99
  "!test/fixtures/*.js"
101
- ]
100
+ ],
101
+ "templateOSS": {
102
+ "version": "2.4.3"
103
+ }
102
104
  }