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

Commit f024962

Browse files
ruyadornoisaacs
authored andcommitted
feat: npm workspaces
Introduces support to workspaces; adding ability to build an ideal tree that links defined workspaces, storing and reading lockfiles that contains workspaces info and also reifying installation trees properly symlinking nested workspaces into place. Handling of the config definitions is done via @npmcli/map-workspaces module added. refs: - https://github.com/npm/rfcs/blob/ea2d3024e6e149cd8c6366ed18373c9a566b1124/accepted/0026-workspaces.md - https://www.npmjs.com/package/@npmcli/map-workspaces - npm/rfcs#103 PR-URL: #50 Credit: @ruyadorno Close: #50 Reviewed-by: @isaacs
1 parent d367a70 commit f024962

File tree

79 files changed

+3029
-43
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+3029
-43
lines changed

lib/arborist/build-ideal-tree.js

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const npa = require('npm-package-arg')
55
const pacote = require('pacote')
66
const semver = require('semver')
77
const pickManifest = require('npm-pick-manifest')
8+
const mapWorkspaces = require('@npmcli/map-workspaces')
89

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

208+
.then(tree => this[_mapWorkspaces](tree))
206209
.then(tree => {
207210
// null the virtual tree, because we're about to hack away at it
208211
// if you want another one, load another copy.
@@ -234,9 +237,19 @@ module.exports = cls => class IdealTreeBuilder extends Tracker(Virtual(Actual(cl
234237
optional: false,
235238
global: this[_global],
236239
legacyPeerDeps: this.legacyPeerDeps,
240+
hasWorkspaces: !!pkg.workspaces,
237241
})
238242
}
239243

244+
[_mapWorkspaces] (node) {
245+
return mapWorkspaces({ cwd: node.path, pkg: node.package })
246+
.then(workspaces => {
247+
if (workspaces.size)
248+
node.workspaces = workspaces
249+
return node
250+
})
251+
}
252+
240253
// process the add/rm requests by modifying the root node, and the
241254
// update.names request by queueing nodes dependent on those named.
242255
[_applyUserRequests] (options) {

lib/arborist/load-virtual.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// mixin providing the loadVirtual method
22

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

56
const consistentResolve = require('../consistent-resolve.js')
67
const Shrinkwrap = require('../shrinkwrap.js')
@@ -15,6 +16,7 @@ const resolveLinks = Symbol('resolveLinks')
1516
const assignParentage = Symbol('assignParentage')
1617
const loadNode = Symbol('loadVirtualNode')
1718
const loadLink = Symbol('loadVirtualLink')
19+
const loadWorkspaces = Symbol('loadWorkspaces')
1820

1921
module.exports = cls => class VirtualLoader extends cls {
2022
constructor (options) {
@@ -39,7 +41,10 @@ module.exports = cls => class VirtualLoader extends cls {
3941
// when building the ideal tree, we pass in a root node to this function
4042
// otherwise, load it from the root package in the lockfile
4143
const {
42-
root = this[loadNode]('', s.data.packages[''] || {})
44+
root = this[loadWorkspaces](
45+
this[loadNode]('', s.data.packages[''] || {}),
46+
s
47+
)
4348
} = options
4449

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

168+
[loadWorkspaces] (node, s) {
169+
const workspaces = mapWorkspaces.virtual({
170+
cwd: node.path,
171+
lockfile: s.data
172+
})
173+
if (workspaces.size)
174+
node.workspaces = workspaces
175+
return node
176+
}
177+
163178
[loadLink] (location, targetLoc, target, meta) {
164179
const path = resolve(this.path, location)
165180
const link = new Link({

lib/edge.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// An edge in the dependency graph
22
// Represents a dependency relationship of some kind
33

4+
const npa = require('npm-package-arg')
45
const depValid = require('./dep-valid.js')
56
const _from = Symbol('_from')
67
const _to = Symbol('_to')
@@ -18,6 +19,7 @@ const types = new Set([
1819
'optional',
1920
'peer',
2021
'peerOptional',
22+
'workspace'
2123
])
2224

2325
class Edge {
@@ -26,6 +28,10 @@ class Edge {
2628

2729
if (typeof spec !== 'string')
2830
throw new TypeError('must provide string spec')
31+
32+
if (type === 'workspace' && npa(spec).type !== 'directory')
33+
throw new TypeError('workspace edges must be a symlink')
34+
2935
this[_spec] = spec
3036

3137
if (accept !== undefined) {

lib/node.js

+29
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const _fsParent = Symbol('_fsParent')
4646
const _reloadEdges = Symbol('_reloadEdges')
4747
const _loadType = Symbol('_loadType')
4848
const _loadDepType = Symbol('_loadDepType')
49+
const _loadWorkspaces = Symbol('_loadWorkspaces')
4950
const _reloadNamedEdges = Symbol('_reloadNamedEdges')
5051
// overridden by Link class
5152
const _loadDeps = Symbol.for('Arborist.Node._loadDeps')
@@ -55,6 +56,7 @@ const _refreshTopMeta = Symbol('_refreshTopMeta')
5556
const _refreshPath = Symbol('_refreshPath')
5657
const _delistFromMeta = Symbol('_delistFromMeta')
5758
const _global = Symbol.for('global')
59+
const _workspaces = Symbol('_workspaces')
5860

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

98+
this[_workspaces] = null
99+
96100
this.errors = error ? [error] : []
97101
const pkg = normalize(options.pkg || {})
98102

@@ -209,6 +213,22 @@ class Node {
209213
return this.global && this.parent.isRoot
210214
}
211215

216+
get workspaces() {
217+
return this[_workspaces]
218+
}
219+
220+
set workspaces(workspaces) {
221+
// deletes edges if they already exists
222+
if (this[_workspaces])
223+
for (const [name, path] of this[_workspaces].entries()) {
224+
if (!workspaces.has(name)) this.edgesOut.get(name).detach()
225+
}
226+
227+
this[_workspaces] = workspaces
228+
this[_loadWorkspaces]()
229+
this[_loadDeps]()
230+
}
231+
212232
get binPaths () {
213233
if (!this.parent)
214234
return []
@@ -242,6 +262,7 @@ class Node {
242262
}
243263

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

358+
[_loadWorkspaces] () {
359+
if (!this[_workspaces]) return
360+
361+
for (const [name, path] of this[_workspaces].entries()) {
362+
new Edge({ from: this, name, spec: `file:${path}`, type: 'workspace' })
363+
}
364+
}
365+
337366
[_loadDeps] () {
338367
// Caveat! Order is relevant!
339368
// packages in optionalDependencies and prod/peer/dev are

lib/shrinkwrap.js

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const pkgMetaKeys = [
104104
'hasInstallScript',
105105
'bin',
106106
'deprecated',
107+
'workspaces',
107108
]
108109

109110
const nodeMetaKeys = [

0 commit comments

Comments
 (0)