Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(link): add workspace support #3312

Merged
merged 1 commit into from
May 26, 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
42 changes: 42 additions & 0 deletions docs/content/commands/npm-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ relevant metadata by running `npm install <dep> --package-lock-only`.
If you _want_ to save the `file:` reference in your `package.json` and
`package-lock.json` files, you can use `npm link <dep> --save` to do so.

### Workspace Usage

`npm link <pkg> --workspace <name>` will link the relevant package as a
dependency of the specified workspace(s). Note that It may actually be
linked into the parent project's `node_modules` folder, if there are no
conflicting dependencies.

`npm link --workspace <name>` will create a global link to the specified
workspace(s).

### Configuration

<!-- AUTOGENERATED CONFIG DESCRIPTIONS START -->
Expand Down Expand Up @@ -261,6 +271,38 @@ commands that modify your local installation, eg, `install`, `update`,
Note: This is NOT honored by other network related commands, eg `dist-tags`,
`owner`, etc.

#### `workspace`

* Default:
* Type: String (can be set multiple times)

Enable running a command in the context of the configured workspaces of the
current project while filtering by running only the workspaces defined by
this configuration option.

Valid values for the `workspace` config are either:

* Workspace names
* Path to a workspace directory
* Path to a parent workspace directory (will result to selecting all of the
nested workspaces)

When set for the `npm init` command, this may be set to the folder of a
workspace which does not yet exist, to create the folder and set it up as a
brand new workspace within the project.

This value is not exported to the environment for child processes.

#### `workspaces`

* Default: false
* Type: Boolean

Enable running a command in the context of **all** the configured
workspaces.

This value is not exported to the environment for child processes.

<!-- AUTOGENERATED CONFIG DESCRIPTIONS END -->

### See Also
Expand Down
1 change: 1 addition & 0 deletions lib/base-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class BaseCommand {
this.wrapWidth = 80
this.npm = npm
this.workspaces = null
this.workspacePaths = null
}

get name () {
Expand Down
11 changes: 8 additions & 3 deletions lib/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const semver = require('semver')

const reifyFinish = require('./utils/reify-finish.js')

const BaseCommand = require('./base-command.js')
class Link extends BaseCommand {
const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js')
class Link extends ArboristWorkspaceCmd {
/* istanbul ignore next - see test/lib/load-all-commands.js */
static get description () {
return 'Symlink a package folder'
Expand Down Expand Up @@ -46,6 +46,7 @@ class Link extends BaseCommand {
'bin-links',
'fund',
'dry-run',
...super.params,
]
}

Expand Down Expand Up @@ -143,12 +144,16 @@ class Link extends BaseCommand {
log: this.npm.log,
add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`),
save,
workspaces: this.workspaces,
})

await reifyFinish(this.npm, localArb)
}

async linkPkg () {
const wsp = this.workspacePaths
const paths = wsp && wsp.length ? wsp : [this.npm.prefix]
wraithgar marked this conversation as resolved.
Show resolved Hide resolved
const add = paths.map(path => `file:${path}`)
const globalTop = resolve(this.npm.globalDir, '..')
const arb = new Arborist({
...this.npm.flatOptions,
Expand All @@ -157,7 +162,7 @@ class Link extends BaseCommand {
global: true,
})
await arb.reify({
add: [`file:${this.npm.prefix}`],
add,
log: this.npm.log,
})
await reifyFinish(this.npm, arb)
Expand Down
1 change: 1 addition & 0 deletions lib/workspaces/arborist-cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class ArboristCmd extends BaseCommand {
getWorkspaces(filters, { path: this.npm.localPrefix })
.then(workspaces => {
this.workspaces = [...workspaces.keys()]
this.workspacePaths = [...workspaces.values()]
this.exec(args, cb)
})
.catch(er => cb(er))
Expand Down
15 changes: 15 additions & 0 deletions tap-snapshots/test/lib/link.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ exports[`test/lib/link.js TAP link global linked pkg to local nm when using args

`

exports[`test/lib/link.js TAP link global linked pkg to local workspace using args > should create a local symlink to global pkg 1`] = `
{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/@myscope/bar -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/global-prefix/lib/node_modules/@myscope/bar
{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/@myscope/linked -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/scoped-linked
{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/a -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/global-prefix/lib/node_modules/a
{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/link-me-too -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/link-me-too
{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/test-pkg-link -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/test-pkg-link
{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/x -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/packages/x

`

exports[`test/lib/link.js TAP link pkg already in global space > should create a local symlink to global pkg 1`] = `
{CWD}/test/lib/tap-testdir-link-link-pkg-already-in-global-space/my-project/node_modules/@myscope/linked -> {CWD}/test/lib/tap-testdir-link-link-pkg-already-in-global-space/scoped-linked

Expand All @@ -28,3 +38,8 @@ exports[`test/lib/link.js TAP link to globalDir when in current working dir of p
{CWD}/test/lib/tap-testdir-link-link-to-globalDir-when-in-current-working-dir-of-pkg-and-no-args/global-prefix/lib/node_modules/test-pkg-link -> {CWD}/test/lib/tap-testdir-link-link-to-globalDir-when-in-current-working-dir-of-pkg-and-no-args/test-pkg-link

`

exports[`test/lib/link.js TAP link ws to globalDir when workspace specified and no args > should create a global link to current pkg 1`] = `
{CWD}/test/lib/tap-testdir-link-link-ws-to-globalDir-when-workspace-specified-and-no-args/global-prefix/lib/node_modules/a -> {CWD}/test/lib/tap-testdir-link-link-ws-to-globalDir-when-workspace-specified-and-no-args/test-pkg-link/packages/a

`
2 changes: 2 additions & 0 deletions tap-snapshots/test/lib/load-all-commands.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,8 @@ Options:
[--strict-peer-deps] [--package-lock]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--ignore-scripts]
[--audit] [--bin-links] [--fund] [--dry-run]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces]

alias: ln

Expand Down
2 changes: 2 additions & 0 deletions tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,8 @@ All commands:
[--strict-peer-deps] [--package-lock]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--ignore-scripts]
[--audit] [--bin-links] [--fund] [--dry-run]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces]

alias: ln

Expand Down
172 changes: 172 additions & 0 deletions test/lib/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,60 @@ t.test('link to globalDir when in current working dir of pkg and no args', (t) =
})
})

t.test('link ws to globalDir when workspace specified and no args', (t) => {
t.plan(2)

const testdir = t.testdir({
'global-prefix': {
lib: {
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
},
},
},
'test-pkg-link': {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0',
workspaces: ['packages/*'],
}),
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
},
},
})
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
npm.prefix = resolve(testdir, 'test-pkg-link')
npm.localPrefix = resolve(testdir, 'test-pkg-link')

reifyOutput = async () => {
reifyOutput = undefined

const links = await printLinks({
path: resolve(npm.globalDir, '..'),
global: true,
})

t.matchSnapshot(links, 'should create a global link to current pkg')
}

// link.workspaces = ['a']
// link.workspacePaths = [resolve(testdir, 'test-pkg-link/packages/a')]
link.execWorkspaces([], ['a'], (err) => {
t.error(err, 'should not error out')
})
})

t.test('link global linked pkg to local nm when using args', (t) => {
t.plan(2)

Expand Down Expand Up @@ -192,6 +246,124 @@ t.test('link global linked pkg to local nm when using args', (t) => {
})
})

t.test('link global linked pkg to local workspace using args', (t) => {
t.plan(2)

const testdir = t.testdir({
'global-prefix': {
lib: {
node_modules: {
'@myscope': {
foo: {
'package.json': JSON.stringify({
name: '@myscope/foo',
version: '1.0.0',
}),
},
bar: {
'package.json': JSON.stringify({
name: '@myscope/bar',
version: '1.0.0',
}),
},
linked: t.fixture('symlink', '../../../../scoped-linked'),
},
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
}),
},
'test-pkg-link': t.fixture('symlink', '../../../test-pkg-link'),
},
},
},
'test-pkg-link': {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0',
}),
},
'link-me-too': {
'package.json': JSON.stringify({
name: 'link-me-too',
version: '1.0.0',
}),
},
'scoped-linked': {
'package.json': JSON.stringify({
name: '@myscope/linked',
version: '1.0.0',
}),
},
'my-project': {
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
workspaces: ['packages/*'],
}),
packages: {
x: {
'package.json': JSON.stringify({
name: 'x',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
},
}),
},
},
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
}),
},
},
},
})
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
npm.prefix = resolve(testdir, 'my-project')
npm.localPrefix = resolve(testdir, 'my-project')

const _cwd = process.cwd()
process.chdir(npm.prefix)

reifyOutput = async () => {
reifyOutput = undefined
process.chdir(_cwd)

const links = await printLinks({
path: npm.prefix,
})

t.matchSnapshot(links, 'should create a local symlink to global pkg')
}

// installs examples for:
// - test-pkg-link: pkg linked to globalDir from local fs
// - @myscope/linked: scoped pkg linked to globalDir from local fs
// - @myscope/bar: prev installed scoped package available in globalDir
// - a: prev installed package available in globalDir
// - file:./link-me-too: pkg that needs to be reified in globalDir first
link.execWorkspaces([
'test-pkg-link',
'@myscope/linked',
'@myscope/bar',
'a',
'file:../link-me-too',
], ['x'], (err) => {
t.error(err, 'should not error out')
})
})

t.test('link pkg already in global space', (t) => {
t.plan(3)

Expand Down