--- title: Dependency Selector Syntax & Querying section: 7 description: Dependency Selector Syntax & Querying --- ### Description The `npm query` commmand exposes a new dependency selector syntax (informed by & respecting many aspects of the [CSS Selectors 4 Spec](https://dev.w3.org/csswg/selectors4/#relational)) which: - Standardizes the shape of, & querying of, dependency graphs with a robust object model, metadata & selector syntax - Leverages existing, known language syntax & operators from CSS to make disparate package information broadly accessible - Unlocks the ability to answer complex, multi-faceted questions about dependencies, their relationships & associative metadata - Consolidates redundant logic of similar query commands in `npm` (ex. `npm fund`, `npm ls`, `npm outdated`, `npm audit` ...) ### Dependency Selector Syntax `v1.0.0` #### Overview: - there is no "type" or "tag" selectors (ex. `div, h1, a`) as a dependency/target is the only type of `Node` that can be queried - the term "dependencies" is in reference to any `Node` found in a `tree` returned by `Arborist` #### Selectors - `*` universal selector - `#<name>` dependency selector (equivalent to `[name="..."]`) - `#<name>@<version>` (equivalent to `[name=<name>]:semver(<version>)`) - `,` selector list delimiter - `.` class selector - `:` pseudo class selector - `>` direct decendent/child selector - `~` sibling selector #### Pseudo Selectors - [`:not(<selector>)`](https://developer.mozilla.org/en-US/docs/Web/CSS/:not) - [`:has(<selector>)`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) - [`:is(<selector list>)`](https://developer.mozilla.org/en-US/docs/Web/CSS/:is) - [`:root`](https://developer.mozilla.org/en-US/docs/Web/CSS/:root) matches the root node/dependency - [`:scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/:scope) matches node/dependency it was queried against - [`:empty`](https://developer.mozilla.org/en-US/docs/Web/CSS/:empty) when a dependency has no dependencies - [`:private`](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#private) when a dependency is private - `:link` when a dependency is linked - `:deduped` when a dependency has been deduped - `:override` when a dependency is an override - `:extraneous` when a dependency exists but is not defined as a dependency of any node - `:invalid` when a dependency version is out of its ancestors specified range - `:missing` when a dependency is not found on disk - `:semver(<spec>)` matching a valid [`node-semver`](https://github.com/npm/node-semver) spec - `:path(<path>)` [glob](https://www.npmjs.com/package/glob) matching based on dependencies path relative to the project - `:type(<type>)` [based on currently recognized types](https://github.com/npm/npm-package-arg#result-object) #### [Attribute Selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) The attribute selector evaluates the key/value pairs in `package.json` if they are `String`s. - `[]` 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... #### `Array` & `Object` Attribute Selectors The generic `:attr()` pseudo selector standardizes a pattern which can be used for attribute selection of `Object`s, `Array`s or `Arrays` of `Object`s accessible via `Arborist`'s `Node.package` metadata. This allows for iterative attribute selection beyond top-level `String` evaluation. The last argument passed to `:attr()` must be an `attribute` selector or a nested `:attr()`. See examples below: #### `Objects` ```css /* return dependencies that have a `scripts.test` containing `"tap"` */ *:attr(scripts, [test~=tap]) ``` #### Nested `Objects` Nested objects are expressed as sequential arguments to `:attr()`. ```css /* return dependencies that have a testling config for opera browsers */ *:attr(testling, browsers, [~=opera]) ``` #### `Arrays` `Array`s specifically uses a special/reserved `.` character in place of a typical attribute name. `Arrays` also support exact `value` matching when a `String` is passed to the selector. ##### Example of an `Array` Attribute Selection: ```css /* removes the distinction between properties & arrays */ /* ie. we'd have to check the property & iterate to match selection */ *:attr([keywords^=react]) *:attr(contributors, :attr([name~=Jordan])) ``` ##### Example of an `Array` matching directly to a value: ```css /* return dependencies that have the exact keyword "react" */ /* this is equivalent to `*:keywords([value="react"])` */ *:attr([keywords=react]) ``` ##### Example of an `Array` of `Object`s: ```css /* returns */ *:attr(contributors, [email=ruyadorno@github.com]) ``` ### Groups Dependency groups are defined by the package relationships to their ancestors (ie. the dependency types that are defined in `package.json`). This approach is user-centric as the ecosystem has been taught to think about dependencies in these groups first-and-foremost. Dependencies are allowed to be apart of multiple groups (ex. a `workspace` may also be a `dev` dependency & may also be `bundled` - a selector for that type of dependency would look like: `*.workspace.dev.bundled`). - `.prod` - `.dev` - `.optional` - `.peer` - `.bundled` - `.workspace` ### Command #### `npm query "<selector>"` (alias `q`) #### Options: - `query-output` - Default: `list` - a human-readable subset of dependency information - `json` - all data available #### Example Response Output - an array of dependency objects is returned which can contain multiple copies of the same package which may or may not have been linked or deduped ```json [ { "name": "", "version": "", "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": [], ... }, ... ``` ### Usage ```bash # get all workspace direct deps npm query ":root > .workspace > *" ``` ### Extended Use Cases & Queries ```stylus // 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 deps named "lodash" & within semver range ^"1.2.3" #lodash@^1.2.3 // equivalent to... [name="lodash"]:semver(^1.2.3) // get the hoisted node for a given semver range #lodash@^1.2.3:not(:deduped) // querying deps with a specific version #lodash@2.1.5 // equivalent to... [name="lodash"][version="2.1.5"] // has any deps :has(*) // deps with no other deps (ie. "leaf" nodes) :empty // manually querying git dependencies [repository^=github:], [repository^=git:], [repository^=https://github.com], [repository^=http://github.com], [repository^=https://github.com], [repository^=+git:...] // querying for all git dependencies :type(git) // get production dependencies that aren't also dev deps .prod:not(.dev) // get dependencies with specific licenses [license=MIT], [license=ISC] // find all packages that have @ruyadorno as a contributor :attr(contributors, [email=ruyadorno@github.com]) ``` ### Piping `npm query` to other commands ```bash # find all dependencies with postinstall scripts & uninstall them npm query ":attr(scripts, [postinstall])" | jq 'map(.name)|join("\n")' -r | xargs -I {} npm uninstall {} # find all git dependencies & explain who requires them npm query ":type(git)" | jq 'map(.name)' | xargs -I {} npm why {} ``` ### Programmatic Usage - `Arborist`'s `Node` Class will have a new `.querySelectorAll()` method - this method will return a filtered, flattened dependency Arborist `Node` list based on a valid query selector - Introduce a new command, `npm query`, which will take a dependency selector & output a flattened dependency Node list (output is in `json` by default, but configurable) ```js const Arborist = require('@npmcli/arborist') const arb = new Arborist({}) ``` ```js // root-level arb.loadActual((tree) => { // query all production dependencies const results = await tree.querySelectorAll('.prod') console.log(results) }) ``` ```js // iterative arb.loadActual((tree) => { // query for the deduped version of react const results = await tree.querySelectorAll('#react:not(:deduped)') // query the deduped react for git deps const deps = await results[0].querySelectorAll(':type(git)') console.log(deps) }) ```