# 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 ```