How observe reads an Nx workspace

The Nx detector of @putnami/observe finds the projects of an Nx repository, the directory each one owns and the projects each one depends on. The change analysis uses them to map each changed file to its project and to list the projects that depend on it. The detector reads files only: it never runs nx or a package manager.

The graph export comes first

When the CI ran nx graph --file=graph.json before putnami observe, the export is the authority. It holds every edge Nx knows, including the ones it computes from source imports, which no static read can see.

npx nx graph --file=graph.json
putnami observe

The detector reads graph.json at the repository root; putnami observe has no flag to name another file:

  • each node of graph.nodes is a project at its data.root;
  • each entry of graph.dependencies between two project nodes is an edge, whether Nx marks it static, dynamic or implicit;
  • a dependency on an external node, such as npm:react, is not an edge.

A missing file is not a finding. A file that is not an Nx graph export, or whose nodes name no root, gets the observe.nx_graph_unreadable warning when the caller named it or the repository holds nx.json, and the detector reads the configuration instead. A node without a root, or with the root of another node, gets the same warning and is skipped.

Without an export: the Nx configuration

A repository whose root holds nx.json is an Nx workspace. Its projects are:

  • every directory that holds a project.json;
  • every directory whose package.json the package manager workspace lists: workspaces in the root package.json, packages in pnpm-workspace.yaml and packages in lerna.json, together. A ! pattern removes directories.

The package.json beside a project.json belongs to that project, listed or not.

The detector does not enter node_modules, dist and hidden directories.

Fact Read from
Name project.json name, else package.json nx.name, else package.json name, else the directory's last segment
Edges implicitDependencies of project.json and of package.json nx: names, * patterns (* alone selects every project), tag:<tag> selectors, and ! exclusions, which also remove a package dependency edge; and the dependencies, devDependencies, peerDependencies and optionalDependencies that name another project's package
Type projectType

An edge that only a source import carries is missing from this read. Write the graph export in the CI to get it.

A project.json or package.json that is not valid JSON gets the observe.nx_manifest_unreadable warning, and its directory is skipped.

What each project tells the change analysis

Member Meaning
name The Nx project name.
source orchestrator when it comes from the graph export, workspace when it comes from the configuration.
testOutputs The repository-relative outputs of the project's test targets, where its test and coverage reports land.
lockfile The root lockfile: pnpm-lock.yaml, yarn.lock, package-lock.json, bun.lock or bun.lockb, the first one found.

A test target is named test, test-*, test:*, *-test or e2e, or its executor ends in :test or runs Jest, Vitest, Playwright or Cypress. Its outputs come from the target, else from the nx.json targetDefaults of its name, else of its executor. The detector resolves {projectRoot}, {workspaceRoot}, {projectName} and {options.<name>}, cuts a glob to the directory before it, and drops an exclusion, an absolute path and an output it cannot resolve.

The probe answer

The detector answers in the shape of a workspace probe result, the contract an extension uses to tell Putnami about the projects of a directory: each project by its repository-relative path, the manifest its name was read from, its dependencies as project paths, and the members above as its metadata. The detector can therefore move to an extension of its own once Putnami can run a probe outside a workspace.