Dependency Selector Syntax & Querying

Dependency Selector Syntax & Querying

Select CLI Version:

Description

The npm query commmand exposes a new dependency selector syntax (informed by & respecting many aspects of the CSS Selectors 4 Spec) 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

Combinators

  • > direct descendant/child
  • any descendant/child
  • ~ sibling

Selectors

  • * universal selector
  • #<name> dependency selector (equivalent to [name="..."])
  • #<name>@<version> (equivalent to [name=<name>]:semver(<version>))
  • , selector list delimiter
  • . dependency type selector
  • : pseudo selector

Dependency Type Selectors

  • .prod dependency found in the dependencies section of package.json, or is a child of said dependency
  • .dev dependency found in the devDependencies section of package.json, or is a child of said dependency
  • .optional dependency found in the optionalDependencies section of package.json, or has "optional": true set in its entry in the peerDependenciesMeta section of package.json, or a child of said dependency
  • .peer dependency found in the peerDependencies section of package.json
  • .workspace dependency found in the workspaces section of package.json
  • .bundled dependency found in the bundleDependencies section of package.json, or is a child of said dependency

Pseudo Selectors

  • :not(<selector>)
  • :has(<selector>)
  • :is(<selector list>)
  • :root matches the root node/dependency
  • :scope matches node/dependency it was queried against
  • :empty when a dependency has no dependencies
  • :private when a dependency is private
  • :link when a dependency is linked (for instance, workspaces or packages manually linked
  • :deduped when a dependency has been deduped (note that this does not always mean the dependency has been hoisted to the root of node_modules)
  • :overridden when a dependency has been overridden
  • :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 spec
  • :path(<path>) glob matching based on dependencies path relative to the project
  • :type(<type>) based on currently recognized types

Attribute Selectors

The attribute selector evaluates the key/value pairs in package.json if they are Strings.

  • [] 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 starts 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 Objects, Arrays or Arrays of Objects 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

/* return dependencies that have a `scripts.test` containing `"tap"` */
*: attr(scripts, [test~=tap]);

Nested Objects

Nested objects are expressed as sequential arguments to :attr().

/* return dependencies that have a testling config for opera browsers */
*: attr(testling, browsers, [~=opera]);

Arrays

Arrays 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:
/* 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:
/* return dependencies that have the exact keyword "react" */
/* this is equivalent to `*:keywords([value="react"])` */
*: attr([keywords=react]);
Example of an Array of Objects:
/* 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 included in multiple groups (ex. a prod dependency may also be a dev dependency (in that it's also required by another dev dependency) & may also be bundled - a selector for that type of dependency would look like: *.prod.dev.bundled).

  • .prod
  • .dev
  • .optional
  • .peer
  • .bundled
  • .workspace

Please note that currently workspace deps are always prod dependencies. Additionally the .root dependency is also considered a prod dependency.

Programmatic Usage

  • Arborist's Node Class has a .querySelectorAll() method
    • this method will return a filtered, flattened dependency Arborist Node list based on a valid query selector
const Arborist = require("@npmcli/arborist");
const arb = new Arborist({});
// root-level
arb.loadActual().then(async (tree) => {
// query all production dependencies
const results = await tree.querySelectorAll(".prod");
console.log(results);
});
// iterative
arb.loadActual().then(async (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);
});

See Also