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

feat: npm workspaces #50

Closed
wants to merge 1 commit into from
Closed
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
13 changes: 13 additions & 0 deletions lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const npa = require('npm-package-arg')
const pacote = require('pacote')
const semver = require('semver')
const pickManifest = require('npm-pick-manifest')
const mapWorkspaces = require('@npmcli/map-workspaces')

const calcDepFlags = require('../calc-dep-flags.js')
const Shrinkwrap = require('../shrinkwrap.js')
Expand Down Expand Up @@ -46,6 +47,7 @@ const _nodeFromSpec = Symbol('nodeFromSpec')
const _fetchManifest = Symbol('fetchManifest')
const _problemEdges = Symbol('problemEdges')
const _manifests = Symbol('manifests')
const _mapWorkspaces = Symbol('mapWorkspaces')
const _linkFromSpec = Symbol('linkFromSpec')
const _loadPeerSet = Symbol('loadPeerSet')
// shared symbols so we can hit them with unit tests
Expand Down Expand Up @@ -203,6 +205,7 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
.then(meta => Object.assign(root, {meta}))
: this.loadVirtual({ root }))

.then(tree => this[_mapWorkspaces](tree))
.then(tree => {
// null the virtual tree, because we're about to hack away at it
// if you want another one, load another copy.
Expand Down Expand Up @@ -234,9 +237,19 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
optional: false,
global: this[_global],
legacyPeerDeps: this.legacyPeerDeps,
hasWorkspaces: !!pkg.workspaces,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where this field is used, is it a leftover from something that changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah def a leftover, will clean it up 😊

})
}

[_mapWorkspaces] (node) {
return mapWorkspaces({ cwd: node.path, pkg: node.package })
.then(workspaces => {
if (workspaces.size)
node.workspaces = workspaces
return node
})
}

// process the add/rm requests by modifying the root node, and the
// update.names request by queueing nodes dependent on those named.
[_applyUserRequests] (options) {
Expand Down
17 changes: 16 additions & 1 deletion lib/arborist/load-virtual.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// mixin providing the loadVirtual method

const {resolve} = require('path')
const mapWorkspaces = require('@npmcli/map-workspaces')

const consistentResolve = require('../consistent-resolve.js')
const Shrinkwrap = require('../shrinkwrap.js')
Expand All @@ -15,6 +16,7 @@ const resolveLinks = Symbol('resolveLinks')
const assignParentage = Symbol('assignParentage')
const loadNode = Symbol('loadVirtualNode')
const loadLink = Symbol('loadVirtualLink')
const loadWorkspaces = Symbol('loadWorkspaces')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just noticing this now, not relevant to this PR, but we should make loadVirtual underscore its symbols like the other mixin classes.


module.exports = cls => class VirtualLoader extends cls {
constructor (options) {
Expand All @@ -39,7 +41,10 @@ module.exports = cls => class VirtualLoader extends cls {
// when building the ideal tree, we pass in a root node to this function
// otherwise, load it from the root package in the lockfile
const {
root = this[loadNode]('', s.data.packages[''] || {})
root = this[loadWorkspaces](
this[loadNode]('', s.data.packages[''] || {}),
s
)
} = options

return this[loadFromShrinkwrap](s, root)
Expand Down Expand Up @@ -160,6 +165,16 @@ module.exports = cls => class VirtualLoader extends cls {
return node
}

[loadWorkspaces] (node, s) {
const workspaces = mapWorkspaces.virtual({
cwd: node.path,
lockfile: s.data
})
if (workspaces.size)
node.workspaces = workspaces
return node
}

[loadLink] (location, targetLoc, target, meta) {
const path = resolve(this.path, location)
const link = new Link({
Expand Down
6 changes: 6 additions & 0 deletions lib/edge.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// An edge in the dependency graph
// Represents a dependency relationship of some kind

const npa = require('npm-package-arg')
const depValid = require('./dep-valid.js')
const _from = Symbol('_from')
const _to = Symbol('_to')
Expand All @@ -18,6 +19,7 @@ const types = new Set([
'optional',
'peer',
'peerOptional',
'workspace'
])

class Edge {
Expand All @@ -26,6 +28,10 @@ class Edge {

if (typeof spec !== 'string')
throw new TypeError('must provide string spec')

if (type === 'workspace' && npa(spec).type !== 'directory')
throw new TypeError('workspace edges must be a symlink')

this[_spec] = spec

if (accept !== undefined) {
Expand Down
29 changes: 29 additions & 0 deletions lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const _fsParent = Symbol('_fsParent')
const _reloadEdges = Symbol('_reloadEdges')
const _loadType = Symbol('_loadType')
const _loadDepType = Symbol('_loadDepType')
const _loadWorkspaces = Symbol('_loadWorkspaces')
const _reloadNamedEdges = Symbol('_reloadNamedEdges')
// overridden by Link class
const _loadDeps = Symbol.for('Arborist.Node._loadDeps')
Expand All @@ -55,6 +56,7 @@ const _refreshTopMeta = Symbol('_refreshTopMeta')
const _refreshPath = Symbol('_refreshPath')
const _delistFromMeta = Symbol('_delistFromMeta')
const _global = Symbol.for('global')
const _workspaces = Symbol('_workspaces')

const relpath = require('./relpath.js')
const consistentResolve = require('./consistent-resolve.js')
Expand Down Expand Up @@ -93,6 +95,8 @@ class Node {
// true if part of a global install
this[_global] = global

this[_workspaces] = null

this.errors = error ? [error] : []
const pkg = normalize(options.pkg || {})

Expand Down Expand Up @@ -209,6 +213,22 @@ class Node {
return this.global && this.parent.isRoot
}

get workspaces() {
return this[_workspaces]
}

set workspaces(workspaces) {
// deletes edges if they already exists
if (this[_workspaces])
for (const [name, path] of this[_workspaces].entries()) {
if (!workspaces.has(name)) this.edgesOut.get(name).detach()
}

this[_workspaces] = workspaces
this[_loadWorkspaces]()
this[_loadDeps]()
}

get binPaths () {
if (!this.parent)
return []
Expand Down Expand Up @@ -242,6 +262,7 @@ class Node {
}

this[_package] = pkg
this[_loadWorkspaces]()
this[_loadDeps]()
// do a hard reload, since the dependents may now be valid or invalid
// as a result of the package change.
Expand Down Expand Up @@ -334,6 +355,14 @@ class Node {
return this[_root] || this
}

[_loadWorkspaces] () {
if (!this[_workspaces]) return

for (const [name, path] of this[_workspaces].entries()) {
new Edge({ from: this, name, spec: `file:${path}`, type: 'workspace' })
}
}

[_loadDeps] () {
// Caveat! Order is relevant!
// packages in optionalDependencies and prod/peer/dev are
Expand Down
1 change: 1 addition & 0 deletions lib/shrinkwrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const pkgMetaKeys = [
'hasInstallScript',
'bin',
'deprecated',
'workspaces',
]

const nodeMetaKeys = [
Expand Down
Loading