-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
376 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
const { resolve } = require('path') | ||
const mapWorkspaces = require('@npmcli/map-workspaces') | ||
const rpj = require('read-package-json-fast') | ||
|
||
const npm = require('./npm.js') | ||
const usageUtil = require('./utils/usage.js') | ||
|
||
const wsCmds = new Map(Object.entries({ | ||
docs: require('./workspaces/default.js'), | ||
doctor: require('./workspaces/default.js'), | ||
diff: require('./workspaces/default.js'), | ||
'dist-tag': require('./workspaces/default.js'), | ||
pack: require('./workspaces/default.js'), | ||
publish: require('./workspaces/default.js'), | ||
repo: require('./workspaces/default.js'), | ||
'set-script': require('./workspaces/default.js'), | ||
unpublish: require('./workspaces/default.js'), | ||
version: require('./workspaces/default.js'), | ||
view: require('./workspaces/default.js'), | ||
ls: require('./workspaces/ls.js'), | ||
})) | ||
|
||
class Workspaces { | ||
constructor (npm) { | ||
this.npm = npm | ||
} | ||
|
||
get usage () { | ||
return usageUtil('npm ws <cmd>') | ||
} | ||
|
||
exec (args, cb) { | ||
this.workspaces(args).then(() => cb()).catch(cb) | ||
} | ||
|
||
async workspaces (args) { | ||
if (npm.flatOptions.global) { | ||
throw Object.assign( | ||
new Error('`npm workspaces` does not support global packages'), | ||
{ code: 'ENOGLOBAL' } | ||
) | ||
} | ||
|
||
if (!args.length) { | ||
throw Object.assign( | ||
new Error('`npm workspaces` needs at least a command to run'), | ||
{ code: 'ENOWSCMD' } | ||
) | ||
} | ||
|
||
const cwd = npm.flatOptions.prefix | ||
const pkg = await rpj(resolve(npm.flatOptions.prefix, 'package.json')) | ||
const workspaces = await mapWorkspaces({ cwd, pkg }) | ||
|
||
if (npm.flatOptions.workspace) { | ||
for (const workspaceArg of npm.flatOptions.workspace) { | ||
for (const [key, path] of workspaces.entries()) { | ||
if (workspaceArg !== key | ||
&& resolve(npm.flatOptions.prefix, workspaceArg) !== path) | ||
workspaces.delete(key) | ||
} | ||
} | ||
} | ||
|
||
let [cmdName, ...cmdArgs] = args | ||
|
||
if (cmdName === 'workspaces') | ||
cmdName = cmdArgs.shift(1) | ||
|
||
if (wsCmds.has(cmdName)) { | ||
await wsCmds.get(cmdName)({ | ||
args: cmdArgs, | ||
cmdName, | ||
npm: this.npm, | ||
workspaces, | ||
}) | ||
return | ||
} | ||
|
||
throw Object.assign( | ||
new Error(`${cmdName} is not a recognized command`), | ||
{ code: 'EBADWSCMD' } | ||
) | ||
} | ||
} | ||
|
||
module.exports = Workspaces |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
const { resolve } = require('path') | ||
|
||
const cmd = ({ npm, path, cmdName, args }) => | ||
new Promise((res, reject) => { | ||
const wsPath = resolve(npm.flatOptions.prefix, path) | ||
npm.localPrefix = wsPath | ||
npm.config.set('prefix', wsPath) | ||
|
||
npm.commands[cmdName](args, err => { | ||
if (err) | ||
reject(err) | ||
|
||
res() | ||
}) | ||
}) | ||
|
||
const runCmd = ({ npm, workspaces, cmdName, args }) => | ||
Promise.all( | ||
[...workspaces.values()] | ||
.map(w => cmd({ npm, path: w, cmdName, args })) | ||
) | ||
|
||
module.exports = runCmd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const npm = require('../npm.js') | ||
|
||
const cmd = (args) => | ||
new Promise((res, reject) => { | ||
npm.commands.ls(args, err => { | ||
if (err) | ||
reject(err) | ||
|
||
res() | ||
}) | ||
}) | ||
|
||
const runCmd = async (workspaces, args) => { | ||
await cmd([...workspaces.keys(), ...args]) | ||
} | ||
|
||
module.exports = runCmd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
### Syntax | ||
|
||
- `npm ws fund` (runs a top-level cmd in all configured workspaces) | ||
- `npm ws fund --workspace=a` (runs a top-level cmd in the context of a workspace) | ||
- `npm fund -w a` (top-level `ws` becomes redundant if using `-w` and may be omitted) | ||
|
||
Adding a top-level `workspaces|ws` command should abstract enough the implementation to make it flexible enough to accomodate future tweaks in the workspace installing algorithm. | ||
|
||
[Update]: Maybe add a separate top-level command for configuration management? e.g: `npm wsc add|rm|ls` | ||
|
||
#### Where to start? | ||
- [ ] Add new `workspaces|ws` command/alias | ||
- [ ] Add new folder `./lib/workspaces/*.js` | ||
- [ ] Add default behavior that sets `prefix` to top-level commands under `./lib/workspaces/default.js` | ||
- [ ] Add `ws run-script` | ||
- [ ] Add `ws install` (ie. **scoped installs**) | ||
- [ ] Add `ws install <pkg>` | ||
- [ ] Add `ws ci` | ||
- [ ] Add `ws update` | ||
- [ ] Add `ws uninstall` | ||
- [ ] Add `ws outdated` (ie. `cd ./ && npm outdated`, might need tweaking Arborist to only load from a specific tree node) | ||
- [ ] Add `ws ls` (ie. `cd ./ && npm ls`) | ||
- [ ] Add `` | ||
|
||
#### What should happen? | ||
- [ ] Should run commands over multiple workspaces | ||
* (no args) run command across **all** workspaces | ||
* (`-w=<workspace-name>` named option) filter by only defined names | ||
- [ ] Should support `--parallel` (defaults to `--serial`) | ||
- ref. https://www.npmjs.com/package/npm-run-all | ||
|
||
#### What happens under the hood? | ||
|
||
``` | ||
./lib/workspaces.js `npm workspaces|ws` | ||
./lib/workspaces/default.js <- default... tries `prefix` + <command> / warn if we couldn't do anything... | ||
./lib/workspaces/install.js <- some cmds require special logic | ||
./lib/workspaces/publish.js <- would set `prefix` & then include ./lib/publish.js | ||
... | ||
# The default behavior is to run the command setting the prefix to workspace realpath, e.g: | ||
npm ws publish -w name | ||
# Might be effectively the same as: | ||
npm publish --prefix=<workspace-name> | ||
# Assuming `npm publish` is a command that won't need special tweaks/impl | ||
npm ws install -w name | ||
# ^--- "scoped install": *only* reify the packages for the workspace defined, e.g: | ||
root: | ||
dependencies: | ||
d@1.0.0 | ||
workspaces: | ||
a -> foo@^1.0.0 -> c@1 | ||
b -> foo@^1.0.1 -> c@2 | ||
$ npm ws install -w a | ||
node_modules | ||
+- a -> ../a | ||
+- c@2 | ||
+- foo@1.0.1 | ||
# NOTE: just be mindful of deduping (ie. you'd get c@2 if all workspaces | ||
# were being installed... you should still get it if you only specify `a`) | ||
# NOTE2: arborist will not place `d` within `node_modules` for | ||
# a "scoped install" | ||
# Adding a new dep to a workspace: | ||
$ npm ws install -w <workspace-name> <pkg> -> ./lib/workspaces/install.js | ||
# ^--- <pkg> will be installed as a | ||
# dep of workspace-name | ||
``` | ||
|
||
#### Adding a new dep to a workspace: | ||
|
||
``` | ||
npm install <pkg> | ||
Arborist | ||
root: | ||
- <pkg> <-- add user request | ||
npm ws install <pkg> -w <workspace-name> | ||
Arborist | ||
root: | ||
- workspace-name: | ||
- <pkg> <-- add user request under workspace-name instead | ||
``` | ||
|
||
#### API: | ||
|
||
``` | ||
npm ws <command> -w|--workspace=<pkg-name|group-alias> | ||
``` | ||
|
||
#### Groups: | ||
|
||
A simple way to refer to a set of workspace by using a single name, e.g: | ||
|
||
``` | ||
. | ||
+- core | ||
+- foo | ||
+- plugins | ||
+- lorem | ||
+- ipsum | ||
``` | ||
|
||
With a root `package.json` defining both workspaces packages and groups: | ||
```json | ||
{ | ||
"name": "workspace-example", | ||
"version": "1.0.0", | ||
"workspaces": { | ||
"groups": { | ||
"plugins": ["lorem", "ipsum"], | ||
"common": ["foo"] | ||
}, | ||
"packages": [ | ||
"core/*", | ||
"plugins/*" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
Running: `npm ws install abbrev -w plugins` effectively means adding abbrev as a dep to both `lorem` and `ipsum` and reifying the tree. |
Oops, something went wrong.