Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

fix(add): properly save new deps where they go #273

Merged
merged 1 commit into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 84 additions & 82 deletions lib/add-rm-pkg-deps.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
// add and remove dependency specs to/from pkg manifest

const removeFromOthers = (name, type, pkg) => {
const others = new Set([
'dependencies',
'optionalDependencies',
'devDependencies',
'peerDependenciesMeta',
'peerDependencies',
])

switch (type) {
case 'prod':
others.delete('dependencies')
break
case 'dev':
others.delete('devDependencies')
others.delete('peerDependencies')
others.delete('peerDependenciesMeta')
break
case 'optional':
others.delete('optionalDependencies')
break
case 'peer':
case 'peerOptional':
others.delete('devDependencies')
others.delete('peerDependencies')
others.delete('peerDependenciesMeta')
break
}

for (const other of others)
deleteSubKey(pkg, other, name)
}

const add = ({pkg, add, saveBundle, saveType}) => {
const add = ({pkg, add, saveBundle, saveType, log}) => {
for (const spec of add)
addSingle({pkg, spec, saveBundle, saveType})
addSingle({pkg, spec, saveBundle, saveType, log})

return pkg
}

const addSingle = ({pkg, spec, saveBundle, saveType}) => {
if (!saveType)
saveType = getSaveType(pkg, spec)
// Canonical source of both the map between saveType and where it correlates to
// in the package, and the names of all our dependencies attributes
const saveTypeMap = new Map([
['dev', 'devDependencies'],
['optional', 'optionalDependencies'],
['prod', 'dependencies'],
['peerOptional', 'peerDependencies'],
['peer', 'peerDependencies'],
])

const addSingle = ({pkg, spec, saveBundle, saveType, log}) => {
const { name, rawSpec } = spec
removeFromOthers(name, saveType, pkg)
const type = saveType === 'prod' ? 'dependencies'
: saveType === 'optional' ? 'optionalDependencies'
: saveType === 'peer' || saveType === 'peerOptional' ? 'peerDependencies'
: saveType === 'dev' ? 'devDependencies'
: /* istanbul ignore next */ null

pkg[type] = pkg[type] || {}
if (rawSpec !== '' || pkg[type][name] === undefined)
pkg[type][name] = rawSpec || '*'
// if the user does not give us a type, we infer which type(s)
// to keep based on the same order of priority we do when
// building the tree as defined in the _loadDeps method of
// the node class.
if (!saveType)
saveType = inferSaveType(pkg, spec.name)

if (saveType === 'prod') {
// a production dependency can only exist as production (rpj ensures it
// doesn't coexist w/ optional)
deleteSubKey(pkg, 'devDependencies', name, 'dependencies', log)
deleteSubKey(pkg, 'peerDependencies', name, 'dependencies', log)
} else if (saveType === 'dev') {
// a dev dependency may co-exist as peer, or optional, but not production
deleteSubKey(pkg, 'dependencies', name, 'devDependencies', log)
} else if (saveType === 'optional') {
// an optional dependency may co-exist as dev (rpj ensures it doesn't
// coexist w/ prod)
deleteSubKey(pkg, 'peerDependencies', name, 'optionalDependencies', log)
} else { // peer or peerOptional is all that's left
// a peer dependency may coexist as dev
deleteSubKey(pkg, 'dependencies', name, 'peerDependencies', log)
deleteSubKey(pkg, 'optionalDependencies', name, 'peerDependencies', log)
}

const depType = saveTypeMap.get(saveType)

pkg[depType] = pkg[depType] || {}
if (rawSpec !== '' || pkg[depType][name] === undefined)
pkg[depType][name] = rawSpec || '*'
if (saveType === 'optional') {
// Affordance for previous npm versions that require this behaviour
pkg.dependencies = pkg.dependencies || {}
pkg.dependencies[name] = pkg.optionalDependencies[name]
}

if (saveType === 'peer' || saveType === 'peerOptional') {
const pdm = pkg.peerDependenciesMeta || {}
Expand All @@ -79,47 +79,49 @@ const addSingle = ({pkg, spec, saveBundle, saveType}) => {
}
}

const getSaveType = (pkg, spec) => {
const {name} = spec
const {
// these names are so lonnnnngggg
devDependencies: devDeps,
optionalDependencies: optDeps,
peerDependencies: peerDeps,
peerDependenciesMeta: peerDepsMeta,
} = pkg

if (peerDeps && peerDeps[name] !== undefined) {
if (peerDepsMeta && peerDepsMeta[name] && peerDepsMeta[name].optional)
return 'peerOptional'
else
return 'peer'
} else if (devDeps && devDeps[name] !== undefined)
return 'dev'
else if (optDeps && optDeps[name] !== undefined)
return 'optional'
else
return 'prod'
// Finds where the package is already in the spec and infers saveType from that
const inferSaveType = (pkg, name) => {
for (const saveType of saveTypeMap.keys()) {
if (hasSubKey(pkg, saveTypeMap.get(saveType), name)) {
if (
saveType === 'peerOptional' &&
(!hasSubKey(pkg, 'peerDependenciesMeta', name) ||
!pkg.peerDependenciesMeta[name].optional)
)
return 'peer'
return saveType
}
}
return 'prod'
}

const deleteSubKey = (obj, k, sk) => {
if (obj[k]) {
delete obj[k][sk]
if (!Object.keys(obj[k]).length)
delete obj[k]
const hasSubKey = (pkg, depType, name) => {
return pkg[depType] && Object.prototype.hasOwnProperty.call(pkg[depType], name)
}

// Removes a subkey and warns about it if it's being replaced
const deleteSubKey = (pkg, depType, name, replacedBy, log) => {
if (hasSubKey(pkg, depType, name)) {
if (replacedBy && log)
log.warn('idealTree', `Removing ${depType}.${name} in favor of ${replacedBy}.${name}`)
delete pkg[depType][name]

// clean up peerDependenciesMeta if we are removing something from peerDependencies
if (depType === 'peerDependencies' && pkg.peerDependenciesMeta) {
delete pkg.peerDependenciesMeta[name]
if (!Object.keys(pkg.peerDependenciesMeta).length)
delete pkg.peerDependenciesMeta
}

if (!Object.keys(pkg[depType]).length)
delete pkg[depType]
}
}

const rm = (pkg, rm) => {
for (const type of [
'dependencies',
'optionalDependencies',
'peerDependencies',
'peerDependenciesMeta',
'devDependencies',
]) {
for (const depType of new Set(saveTypeMap.values())) {
for (const name of rm)
deleteSubKey(pkg, type, name)
deleteSubKey(pkg, depType, name)
}
if (pkg.bundleDependencies) {
pkg.bundleDependencies = pkg.bundleDependencies
Expand All @@ -130,4 +132,4 @@ const rm = (pkg, rm) => {
return pkg
}

module.exports = { add, rm }
module.exports = { add, rm, saveTypeMap, hasSubKey }
1 change: 1 addition & 0 deletions lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
saveBundle,
saveType,
path: this.path,
log: this.log,
})
})
}
Expand Down
3 changes: 3 additions & 0 deletions lib/arborist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
const {resolve} = require('path')
const {homedir} = require('os')
const procLog = require('../proc-log.js')
const { saveTypeMap } = require('../add-rm-pkg-deps.js')

const mixins = [
require('../tracker.js'),
Expand Down Expand Up @@ -57,6 +58,8 @@ class Arborist extends Base {
packumentCache: options.packumentCache || new Map(),
log: options.log || procLog,
}
if (options.saveType && !saveTypeMap.get(options.saveType))
throw new Error(`Invalid saveType ${options.saveType}`)
this.cache = resolve(this.options.cache)
this.path = resolve(this.options.path)
process.emit('timeEnd', 'arborist:ctor')
Expand Down
46 changes: 38 additions & 8 deletions lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
const onExit = require('../signal-handling.js')
const pacote = require('pacote')
const rpj = require('read-package-json-fast')
const { updateDepSpec } = require('../dep-spec.js')
const AuditReport = require('../audit-report.js')
const {subset} = require('semver')
const {subset, intersects} = require('semver')
const npa = require('npm-package-arg')

const {dirname, resolve, relative} = require('path')
Expand All @@ -28,6 +27,7 @@ const promiseAllRejectLate = require('promise-all-reject-late')
const optionalSet = require('../optional-set.js')
const updateRootPackageJson = require('../update-root-package-json.js')
const calcDepFlags = require('../calc-dep-flags.js')
const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')

const _retiredPaths = Symbol('retiredPaths')
const _retiredUnchanged = Symbol('retiredUnchanged')
Expand Down Expand Up @@ -959,6 +959,7 @@ module.exports = cls => class Reifier extends cls {
const spec = subSpec ? subSpec.rawSpec : rawSpec
const child = root.children.get(name)

let newSpec
if (req.registry) {
const version = child.version
const prefixRange = version ? this[_savePrefix] + version : '*'
Expand All @@ -972,14 +973,15 @@ module.exports = cls => class Reifier extends cls {
? prefixRange : spec
const pname = child.package.name
const alias = name !== pname
updateDepSpec(pkg, name, (alias ? `npm:${pname}@` : '') + range)
newSpec = alias ? `npm:${pname}@${range}` : range
} else if (req.hosted) {
// save the git+https url if it has auth, otherwise shortcut
const h = req.hosted
const opt = { noCommittish: false }
const save = h.https && h.auth ? `git+${h.https(opt)}`
: h.shortcut(opt)
updateDepSpec(pkg, name, save)
if (h.https && h.auth)
newSpec = `git+${h.https(opt)}`
else
newSpec = h.shortcut(opt)
} else if (req.type === 'directory' || req.type === 'file') {
// save the relative path in package.json
// Normally saveSpec is updated with the proper relative
Expand All @@ -988,9 +990,37 @@ module.exports = cls => class Reifier extends cls {
// thing, so just get the ultimate fetchSpec and relativize it.
const p = req.fetchSpec.replace(/^file:/, '')
const rel = relpath(root.realpath, p)
updateDepSpec(pkg, name, `file:${rel}`)
newSpec = `file:${rel}`
} else
updateDepSpec(pkg, name, req.saveSpec)
newSpec = req.saveSpec

if (options.saveType) {
const depType = saveTypeMap.get(options.saveType)
pkg[depType][name] = newSpec
// rpj will have moved it here if it was in both
// if it is empty it will be deleted later
if (options.saveType === 'prod' && pkg.optionalDependencies)
delete pkg.optionalDependencies[name]
} else {
if (hasSubKey(pkg, 'dependencies', name))
pkg.dependencies[name] = newSpec

if (hasSubKey(pkg, 'devDependencies', name)) {
pkg.devDependencies[name] = newSpec
// don't update peer or optional if we don't have to
if (hasSubKey(pkg, 'peerDependencies', name) && !intersects(newSpec, pkg.peerDependencies[name]))
pkg.peerDependencies[name] = newSpec

if (hasSubKey(pkg, 'optionalDependencies', name) && !intersects(newSpec, pkg.optionalDependencies[name]))
pkg.optionalDependencies[name] = newSpec
} else {
if (hasSubKey(pkg, 'peerDependencies', name))
pkg.peerDependencies[name] = newSpec

if (hasSubKey(pkg, 'optionalDependencies', name))
pkg.optionalDependencies[name] = newSpec
}
}
}

// refresh the edges so they have the correct specs
Expand Down
43 changes: 0 additions & 43 deletions lib/dep-spec.js

This file was deleted.

Loading