Skip to content

Commit

Permalink
Update RFC with latest proposed ideas
Browse files Browse the repository at this point in the history
A lot of brainstorming sessions and OpenRFC call discussions have led to
a number of substantial changes to the original proposed RFC. I'm now
committing these changes in here so that we can further discuss and iron
out any details while we progress with the work in the implementation.

Ref: npm/cli#2765
  • Loading branch information
ruyadorno committed Feb 24, 2021
1 parent cb9ca14 commit d207867
Showing 1 changed file with 96 additions and 106 deletions.
202 changes: 96 additions & 106 deletions accepted/0000-workspaces-part2.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,103 @@ Following up from the original **npm workspaces** RFC are the following command-

## Rationale and Alternatives

The ability to run **npm commands** across defined **workspaces** is essential to successfully manage a **npm workspaces** workflow. That makes the following alternatives much less desireable:
The ability to run **npm commands** across defined **workspaces** is essential to successfully manage a **npm workspaces** workflow.

- Do not implement further support to manage running commands across **workspaces**
- Implement only _some_ of the implementation changes described in this document
Following are possible alternatives to provide this functionality:

**Note:** Alternative ways of executing each of the described features are defined along with their implementation details below.
- Do not implement further support to manage running commands across **workspaces**, keep the **npm workspaces** set of features to a minimum
- Defer to other tools from the ecosystem such as Lerna to solve the problem of running top-level commands across **workspaces**
- Implement the proposed features of this RFC as a standalone package / separated tool from the **npm cli**

## Implementation

### 1. Run commands across all child packages

Make **npm cli** subcommands **npm workspaces**-aware, so that running a command tries to run it within all **workspaces** as long as a **workspaces configuration** field is properly defined in `package.json`.
Create a new **npm cli** subcommand named **workspaces** (aliased to **ws**) that can route any of the supported subcommands to run in the context of the configured **workspaces** as long as a **workspaces configuration** field is properly defined in `package.

Only a subset of commands are to be supported:
We identified 5 different categories of subcommands based on how they're expected to work:

- `fund` List funding info for all **workspaces**
- `ls` List all packages including **workspaces**
#### 1. Commands that just need context from package.json

Commands that, from an user point of view, are the equivalent of: `cd <workspace-name> && npm <cmd>`.

- `docs`
- `diff` Package diff in the context of specific **workspaces**
- `dist-tag` List dist-tags for specific **workspaces**
- `exec` Run exec in the context of specific **workspaces**
- `init` Initialize a new **workspace**
- `outdated` List outdated **dependencies** including **workspaces** and its **dependencies**
- `run-script` Run arbitrary **scripts** in all **workspaces**, skip any **workspace** that does not have a targetting **script**
- `pack` Run pack in the context of specific **workspaces**
- `publish` Run publish in the context of specific **workspaces**
- `run-script|restart|start|stop|test` Run arbitrary **scripts** in all **workspaces**, skip any **workspace** that does not have a targetting **script**
- `repo`
- `rebuild` Rebuild all **workspaces**
- `restart`
- `start`
- `stop`
- `test` Run test **scripts** in all **workspaces**
- `set-script`
- `update` Updates a **dependency** across the entire installation tree, including **workspaces**
- `unpublish`
- `version` Run version in the context of specific **workspaces**
- `view` View registry info, also including **workspaces**

#### 2. Custom implementation

##### 2.1. Reads from installed dependency tree

General class of helper commands that load from the installed tree to produce some form of useful output.

- `audit`
- `explain`
- `fund` List funding info for all **workspaces**
- `ls` List all packages including **workspaces**

##### 2.2. Modify installed dependency tree

The set of commands that will modify an install tree (from an implementer point of view, these are just [arborist.reify](https://github.com/npm/arborist/) proxies).

- `ci`
- `dedupe|find-dupes`
- `install-ci-test`
- `install-test`
- `install`
- `link`
- `uninstall`
- `update`

##### 2.3. Other

A command that needs a special/custom workspace-aware implementation outside of the context of reading/writing to the install tree (using **Arborist**).

#### 3. Unsupported

This category of **npm cli** subcommand is completely unrelated to anything that the current working directory could affect. All registry helper/management types of commands fall into this category and it's a best UX to just exit with an error code in order to let the end user aware that trying to run these in the context of workspaces don't have any effect.

- `adduser|login`
- `bin`
- `birthday`
- `cache`
- `completion`
- `config|get|set`
- `deprecate`
- `doctor`
- `edit`
- `explore`
- `help`
- `help-search`
- `hook`
- `logout`
- `org`
- `owner`
- `ping`
- `prefix`
- `profile`
- `search`
- `shrinkwrap`
- `star`
- `team`
- `token`
- `unstar`
- `whoami`
- `workspaces`

#### Test example:

```
Expand All @@ -68,25 +138,15 @@ npm ERR! Test failed. See above for more details.
> done
```

**NOTE:** `publish` and `version` are very special cases in a **npm workspaces** setup and are outside of the scope of this RFC. These features will live in userland in the form of popular modules such as [Lerna](https://lerna.js.org/) for the foreseeable future.

### 2. Filter a subset of workspaces

Filter is done via named argument (`--workspace`, short: `-w`) and here are some of the reasons why we decided to go that route:
- [Lerna filters](https://www.npmjs.com/package/@lerna/filter-options) were the starting point but early in the discussion some other considerations were brought forward
- npm configs do have some baggage, configs might apply to to other cli commands and can also possibly be defined project or system-wide - that said, there are things we can do to avoid these pitfalls (such as making sure `npm_config_workspace` env varibable never gets set in `run-script`)
- During the many discussions around this RFC a few strong points for named arguments stand out:
- Allow us to keep `npm workspace` for other command usages
- npm users are more familiar with usage of named configs, example: `npm test --workspace=foo` is very similar to `npm test --prefix=./packages/foo` - if you think on it in terms of "what workspace am I in" again, a named config makes more sense in npm
- [Organized a poll](https://github.com/npm/rfcs/issues/160) in which named arguments were the leading option, here are the results for reference:
```
1. (12 votes) 21% - Positional argument with one-char alias: `npm workspace foo test` or `npm w foo test`
2. (11 votes) 11% - Positional argument but shorter default name: `npm ws foo test` or `npm w foo test`
3. (13 votes) 13% - Special character (operator-like): `npm :foo test`
4. (20 votes) 35% - Named argument: `npm --workspace=foo test` or `npm -w=foo test`
```

Given the results of all this preliminar work, the preferred way to run a command in the context of a workspace is to use the named `--workspace` argument or its `-w` short alias, e.g:
- [Lerna filters](https://www.npmjs.com/package/@lerna/filter-options) were the starting point but early in the discussion some other considerations were brought forward, specially the many pitfalls of supporting globs in the variety of supported shells and operational systems.
- npm configs do have some baggage, configs might apply to to other cli commands and can also possibly be defined project or system-wide so the most sure way to avoid these pitfalls is for configs to only be used in the context of a single **npm cli** subcommand.

### 3. Examples

Given the results of all this preliminar work, the preferred way to run a command in the context of a single workspace is to use the named `--workspace` argument or its `-w` short alias, e.g:

In a project with the following structure:
```
Expand All @@ -102,84 +162,20 @@ In a project with the following structure:
You can run tests for the `foo` workspace from the root of your project, with the following syntax:

```
npm test --workspace=foo
npm ws test --workspace=foo
```


#### Install dependency example:

Add `tap` as a **dependency** of a **workspace** named `foo`:
Add `tap` as a **dependency** of all your configured **workspaces**:

```sh
npm install tap --workspace=foo
npm ws install tap
```


Note: **Globs** are not supported as a valid `--workspace` argument value, the alternative is to use `npm <cmd> --workspace=<alias>` in which the `alias` can be defined beforehand in your **workspace** configuration as detailed in the following up section.

### 3. Grouping/Aliasing workspaces

One proposed feature that was surfaced during our brainstorm sessions is the ability for users to create arbitrary groups of **workspaces** defined via **workspaces configuration**, e.g:

Given a **npm workspace** setup with the following file structure:

```
/root
├── core
│ ├── foo
│ ├── bar
│ └── util
└── plugins
├── helpers
├── lorem
└── ipsum
```

It's possible to define a **groups** property within your **workspaces configuration** that will allow defining named sets of **workspaces** that can be later on used to run commands, e.g:

```
{
"name": "workspace-example",
"version": "1.0.0",
"workspaces": {
"groups": {
"core": ["core/*"], // accepts globs
"plugins": ["lorem", "ipsum"], // also accepts workspaces names
"common": ["util", "helpers"]
},
"packages": [
"core/*",
"plugins/*"
]
}
}
```

Following up with the previous configuration example, it would be possible to run tests across all **workspaces** from the `plugins` group, as following:

```
$ npm test --workspace=plugins
> [email protected] test /root/plugins/lorem
> done
> [email protected] test /root/plugins/ipsum
> done
```

Further example, install a **peer dependency** across a group of packages named `core`:

```
$ npm install react@16 --save-peer --workspace=core
```

**TBD:** It might also be very handy being able to define groups as cli arguments, so that the following example will be equivalent to the previous examples:

```
$ npm workspace core install react@16 --save-peer --workspace-group core=core/*
```

This is a currently unresolved question since pitfalls of glob usage in cli arguments have already surfaced many times during the time of discussion for this RFC.
Note: **Globs** are not supported as a valid `--workspace` argument value, the proposed alternative is to use `npm ws <cmd> --workspace=<dir>` in which `<dir>` is a folder containing multiple workspaces.

## Prior Art

Expand All @@ -190,9 +186,10 @@ This is a currently unresolved question since pitfalls of glob usage in cli argu
#### Filtering a subset of workspaces

- [lerna filter-options](https://www.npmjs.com/package/@lerna/filter-options)
- [Yarn v1 workspace](https://classic.yarnpkg.com/en/docs/cli/workspace)
- [Yarn v1 workspace cmd](https://classic.yarnpkg.com/en/docs/cli/workspace)
- [Yarn v1 workspaces cmd](https://classic.yarnpkg.com/en/docs/cli/workspaces)
- [Yarn v2 foreach include/exclude](https://yarnpkg.com/cli/workspaces/foreach)
- [pnpm recursive --filter](https://pnpm.js.org/en/cli/recursive#filter-lt-package_selector)
- [pnpm Filtering](https://pnpm.js.org/en/filtering)

## Dictionary

Expand All @@ -212,10 +209,3 @@ During the discussions around this RFC it was brought up to our attention that a
- **[globs](https://en.wikipedia.org/wiki/Glob_(programming))**: String patterns that specifies sets of filenames with special characters.
- **[Arborist](https://github.com/npm/arborist)**: The npm@7 install library
- **hoisting packages**: Bringing packages up a level in the context of an installation tree.

## Unresolved Questions and Bikeshedding

- Groups as a cli argument: `--workspace-group`
- how to define the `key=value` nature of it?
- is there any arg currently in the cli using key/value pairs?
- maybe it's not worth having it?

0 comments on commit d207867

Please sign in to comment.