# Dependency Selector Syntax
## Summary
A new dependency object model & selector syntax, informed by & respecting many aspects of the [CSS Selectors 4 Spec](https://dev.w3.org/csswg/selectors4/#relational).
## Motivation
- Standardize a dependency object & selector syntax
- Make it easy to anwser complex questions about your dependencies
- Unblock users from requiring new & redundant query commands in npm (ex. `npm fund`, `npm ls`, `npm outdated`, `npm audit` ...)
## Detailed Explanation
Should only be limited to whatever is possible within our current understanding of CSS Selectors today.
## Implementation
- `Arborist`'s `Node`s have a new `querySelectorAll()` method
- net new implementation of the query selection language described below, available through the `npm query` command.
### Selectors
- `*` all packages
- `>` direct dependants
- `#` id
- `#<name>`
- `#<name @ semver>`
- `.` class
- `:` pseudo class
- `~` sibling selector
- `+` preceding selector
- `[]` attribute selector (ie. existence of attribute)
- `[attribute=value]` attribute value is equivalant...
- `[attribute~=value]` attribute value contains word...
- `[attribute*=value]` attribute value contains string...
- `[attribute|=value]` attribute value is equal to or starts with...
- `[attribute^=value]` attribute value begins with...
- `[attribute$=value]` attribute value ends with...
- `:not(<selector>)`
- `:has(<selector>)`
- `:nth-child(<num>)`
- `:nth-of-type(<num>)`
- `:root`
- `:scope`
- `:empty`
- `:private`
- `:linked`
- `:deduped`
- `:override`
- `:extraneous`
- `:outdated`
- `:vulnerable`
- `:advisory(<id>)`
- `:cwe(<id>)`
- `:semver(<spec>)`
### Classes
Classifications of dependencies; notably, packages can have multiple classes.
- `.prod`
- `.dev`
- `.optional`
- `.peer`
- `.bundled`
- `.workspace`
### Attributes
- `[name]`
- `[version]`
- `[description]`
- `[homepage]`
- `[bugs]`
- `[author]`
- `[license]`
- `[funding]`
- `[files]`
- `[main]`
- `[browser]`
- `[bin]`
- `[man]`
- `[directories]`
- `[repository]`
- `[scripts]`
- `[config]`
- `[workspaces]`
- `[dependencies]`
- `[devDependencies]`
- `[optionalDependencies]`
- `[bundledDependencies]`
- `[peerDependencies]`
- `[peerDependenciesMeta]`
- `[engines]`
- `[os]`
- `[cpu]`
- `[keywords]`
- `[path]`
## New Command
### `npm query "<selector>"` (alias `q`)
#### Options:
- `output` (alias `o`)
- Default: `parseable`
- Type: `parseable`, `json`, `list`, `explain`, `outdated`, `funding`, `audit`, `audit-explain`, `duplicates`, `file`
- `destination` (alias `dest`)
- Default: `./npm-query-{date-time}.json`
- Type: File path
### Command Mapping to `output`
Previous commands with similar behaivours will now all utilize `query` under-the-hood.
### `npm list`
```bash
npm list # equivalent to...
npm query ":root > *"
npm list --all # equivalent to...
npm query "*" --output list
npm list <pkg> # equivalent to...
npm query "#<pkg>" --output list
```
### `npm explain`
```bash
npm explain <pkg> # equivalent to...
npm query "#<pkg>" --output explain
```
### `npm outdated`
```bash
npm outdated # equivalent to...
npm query ":root > *:outdated" --output outdated
npm outdated --all # equivalent to...
npm query "*:outdated" --output outdated
```
### `npm fund`
```bash
npm fund # equivalent to...
npm query ":root > *[funding]" --output funding
npm fund --all # equivalent to...
npm query "*[funding]" --output funding
npm fund <pkg> # equivalent to...
npm query "#<pkg>" --output funding
```
### `npm audit`
```bash
npm audit # equivalent to...
npm query ":root > *:vulnerable" --output audit
npm audit --all # equivalent to...
npm query "*:vulnerable" --output audit
npm audit <pkg> # equivalent to...
npm query "#<pkg>" --output audit
```
### `npm audit explain`
```bash
npm audit explain # equivalent to...
npm query ":root > *:vulnerable" --output audit-explain
npm audit explain --all # equivalent to...
npm query "*:vulnerable" --output audit-explain
npm audit explain <pkg> # equivalent to...
npm query "#<pkg>" --output audit-explain
```
### `npm find-dupes`
```bash
npm find-dupes # equivalent to...
npm query "*:vulnerable" --output duplicates
```
### `npm view`
```bash
npm view # equivalent to...
npm query ":root" --output view
npm view <pkg> # equivalent to...
npm query "#<pkg>" --output view
```
## Example Queries & Use Cases
```sass
// all deps
*
// all direct deps
:root > *
// direct production deps
:root > .prod
// direct development deps
:root > .dev
// any peer dep of a direct deps
:root > * > .peer
// any workspace dep
.workspace
// all workspaces that depend on another workspace
.workspace > .workspace
// all workspaces that have peer deps
.workspace:has(*.peer)
// any dep named "lodash"
// equivalent to [name="lodash"]
#lodash
// any dep named "lodash" & version "1.2.3"
// equivalent to [name="lodash"][version="1.2.3"]
#lodash@1.2.3
[name="lodash"]:semver(1.2.3)
// querying by semver
#lodash@^2
[name="lodash"][version="^2"]
// all deps living alongside vulnerable deps
*:vulnerable ~ *:not(:vulnerable)
// has any deps
*:has(*)
// has any vulnerable deps
*:has(*:vulnerable)
// deps with no other deps (ie. "leaf" nodes)
*:empty
// all vulnerable deps that aren't dev deps & that aren't vulnerable to CWE-1333
*:vulnerable:not(.dev:cwe(1333))
// manually querying git dependencies
*[repository^="github:"],
*[repository^="git:"],
*[repository^="https://github.com"],
*[repository^="http://github.com"],
*[repository^="https://github.com"],
*[repository^="+git:..."]
// querying git dependencies
*:git
// find all references to "install" scripts
*[scripts=install],
*[scripts=postinstall],
*[scripts=preinstall]
// ------------------------------
// brainstorming w/ fritzy
*:dep(.node_module, :has(.package.vulnerability))
:root>(node_module).package
// assumptions:
// querying on-disk representation...
// everything is a package...
// direct dependents
:root > *
// any package I can access in this scope...
*
// :not()
.prod:not(.dev)
.prod:not([name="lodash"])
// license querying (RFC)
*[license="MIT"], *[license="ISC"]
```
## Example Output
```bash
npm query ":root > .workspace > *" # get all workspace direct deps
```
```json
[
{
"hash": "...",
"name": "react",
"version": "1.0.1",
"description": "",
"homepage": "",
"bugs": "",
"author": "",
"license": "",
"funding": "",
"files": "",
"main": "",
"browser": "",
"bin": "",
"man": "",
"directories": "",
"repository": "",
"scripts": "",
"config": "",
"dependencies": "",
"devDependencies": "",
"optionalDependencies": "",
"bundledDependencies": "",
"peerDependencies": "",
"peerDependenciesMeta": "",
"engines": "",
"os": "",
"cpu": "",
"workspaces": "",
"keywords": "",
"vulnerabilities": "",
"cwe": "",
"path": ""
},
...
```
## Prior Art
- https://pnpm.io/filtering
- [`HTML`](https://html.spec.whatwg.org/) & [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)
- [`CSS`](https://www.w3.org/Style/CSS/specs.en.html), [Selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) & [Pseudo Classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
- [`estree`](https://github.com/estree/estree)
- [`abstract-syntax-tree`](https://www.npmjs.com/package/abstract-syntax-tree)
- [`postcss-selector-parser`](https://www.npmjs.com/package/postcss-selector-parser) / [API](https://github.com/postcss/postcss-selector-parser/blob/master/API.md)
- [`css-selector-parser`](https://www.npmjs.com/package/css-selector-parser)
- [`@npmcli/arborist`'s `Node` Class](https://github.com/npm/cli/tree/latest/workspaces/arborist)
- https://github.com/nodejs/Gzemnid
## Unresolved Questions and Bikeshedding
- Q. Is there any such thing as a bare specifier?
- A. No. Unlike CSS Selectors, there's no `"element"`, or `"package"` in this context, equivalent. The selector syntax presuposes all entities are packages.
- Q. Should this syntax cover **all** possible queries of a dependency graph?
- A. No. This spec is meant to provide a sufficently mature mechanism/building block for (re. 80/20 rule applies here)
---
# [RFC] Commands read from `stdin`
```
audit, bugs, ci, config, deprecate, diff, dist-tag, docs, edit, exec,
explain, explore, find-dupes, fund, get, install, install-ci-test,
install-test, link, list, outdated, pack, pkg, prune, publish, rebuild,
repo, restart, run-script, set, set-script, shrinkwrap, star, stars,
start, stop, team, test, token, uninstall, unpublish, unstar, update,
version, view
```
All of the above commands would add the ability to read from `stdin` & execute as if they were passed package specs.
## Example piping output to other commands
This is predicated/dependant on [RFC #]()
```bash=
# list workspaces w/ peer deps
npm query ".workspace:has(*.peer)" | npm ls
# install the same dev deps across all workspaces
npm query ":root > *.dev" | npm install --workspaces
# find and enforce a single version of dependencies
npm query ""
# find cve
npm query "*:cve(<id>)"
# show audit details for vulnerable deps that aren't ReDoS dev deps
npm query "*:vulnerable:not(.dev[cwe~=1333]:not(cwe=))" | npm audit explain
```
---
# [RFC] `npm audit` Improvements
## Commands
### `npm audit [<pkg>]`
### `npm audit fix`
### `npm audit explain [<pkg>]`
---
# [RFC] Scoped Command Config
- must use canonical command name (ie. does not support alias')
### Examples
```ini
omit=dev
[npm-install]
audit=false
fund=false
[npm-audit]
production=true
[npm-audit-fix]
force=true
[npm-list]
all=true
[npm-publish]
loglevel=silly
```