DEPENDENCY()

NAME
Dependency

  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

    •   . dependency type selector

    •   : pseudo selector

    •   > direct decendent/child selector

    •   ~ sibling 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>) 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 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 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 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 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

    [
      {
        "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

    # get all workspace direct deps
    npm query ":root > .workspace > *"

  Extended Use Cases & Queries

    // 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

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

    const Arborist = require('@npmcli/arborist')
    const arb = new Arborist({})

    // root-level
    arb.loadActual((tree) => {
      // query all production dependencies
      const results = await tree.querySelectorAll('.prod')
      console.log(results)
    })

    // 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)
    })