npm package: @monorepo-tooling/sync-ts-project-refs
Description: <!-- markdownlint-disable MD034 MD029 -->
Latest version: 0.1.3

<!-- markdownlint-disable MD034 MD029 -->

# stspr (sync-ts-project-refs)

stspr is a **monorepo tool** that keeps **TypeScript project references** (`references`) in sync with your **pnpm workspace dependencies** (`workspace:*`).

It scans your workspace packages, reads `package.json` dependency graphs, and updates each package’s `tsconfig.json` (and optional `tsconfig.*.json`) so that TypeScript builds and editor tooling can understand project boundaries.

## What it does

- **Adds/removes `references`** in tsconfig files based on `workspace:*` dependencies
- **Supports solution-style root aggregation** (a root “solution” tsconfig that references all packages)
- **Supports per-package canonical tsconfig** (what other packages should reference)
- **Supports per-tsconfig overrides** (extra/skip refs, include deps refs, exclude a tsconfig)
- **Preserves comments** in `tsconfig.json` (JSONC)
- **Supports CI checks** (`--check`) and previews (`--dry-run`)

## Requirements

- A pnpm workspace with `pnpm-workspace.yaml`
- Packages that should participate must have a `tsconfig.json`
- Workspace dependencies must use `workspace:*` (or other `workspace:` ranges)

**Zero configuration required** — stspr works out of the box with sensible defaults. Configuration files are optional and only needed for advanced use cases.

## Installation

Install from npm:

```bash
# pnpm
pnpm add -D @monorepo-tooling/sync-ts-project-refs

# npm
npm install -D @monorepo-tooling/sync-ts-project-refs

# yarn
yarn add -D @monorepo-tooling/sync-ts-project-refs
```

This package provides two equivalent commands: `stspr` and `sync-ts-project-refs`

## CLI usage

### Run

```bash
stspr
```

### Preview changes

```bash
stspr --dry-run --verbose
```

### CI check (no writes, non-zero if changes needed)

```bash
stspr --check
```

### Options

- **`--help, -h`**: show help
- **`--dry-run, -d`**: compute changes but do not write files
- **`--check`**: verify no changes needed (like `--dry-run` + fail if changes would be made)
- **`--verbose, -v`**: print detailed processing info
- **`--workspace-root, -r <path>`**: run against a specific workspace root (otherwise auto-detect via searching for `pnpm-workspace.yaml`)

## Configuration files (optional)

All configuration files below are **optional**. stspr uses sensible defaults and will work without any configuration. Use these files only when you need to customize behavior.

All config keys are **camelCase**.

### Root config: `stspr.root.yaml`

**Location**: workspace root (next to `pnpm-workspace.yaml`)

**Purpose**:

- Configure transitive deps handling
- Configure global hard excludes (packages / tsconfigs)
- Configure which root solution tsconfig to update, and add/skip refs on that file

```yaml
# yaml-language-server: $schema=https://unpkg.com/@monorepo-tooling/sync-ts-project-refs@latest/schemas/stspr.root.schema.json

graph:
  includeIndirectDeps: false

filters:
  # pnpm-workspace style glob patterns, supports "!glob"
  # If a path matches, it is HARD excluded regardless of per-package/per-tsconfig config.
  excludePackages: []
  excludeTsconfigs: []

rootSolution:
  # Root solution tsconfig to update. If it doesn't exist, stspr skips root updating.
  tsconfigPath: ./tsconfig.json
  # Include sibling root tsconfig.*.json as references (discovery)
  includeSiblings: true
  # References that apply ONLY to the root solution tsconfig file
  references:
    add: []
    skip: []
```

### Package config: `stspr.package.yaml`

**Location**: package root (next to `package.json`)

**Purpose**:

- Exclude a package from processing
- Configure canonical tsconfig (what other packages should reference)
- Control which dependency sections are treated as reference edges
- Add/skip references that apply only to the canonical tsconfig

```yaml
# yaml-language-server: $schema=https://unpkg.com/@monorepo-tooling/sync-ts-project-refs@latest/schemas/stspr.package.schema.json

exclude: false

canonicalTsconfig:
  # Default: ./tsconfig.json
  path: ./tsconfig.json
  includeSiblings: true
  # Derived default:
  # - path != ./tsconfig.json => true
  # - path == ./tsconfig.json => false
  # standardReferencesCanonical: true
  includeWorkspaceDeps: true

dependencies:
  include:
    dependencies: true
    devDependencies: true
    optionalDependencies: true

references:
  add: []
  skip: []
```

### Tsconfig-level config: `tsconfig.stspr.yaml` / `tsconfig.{name}.stspr.yaml`

**Location**: same directory as the corresponding `tsconfig*.json`

**Naming**:

- `tsconfig.json` → `tsconfig.stspr.yaml`
- `tsconfig.web.json` → `tsconfig.web.stspr.yaml`
- `tsconfig.custom.json` → `tsconfig.custom.stspr.yaml`

```yaml
# yaml-language-server: $schema=https://unpkg.com/@monorepo-tooling/sync-ts-project-refs@latest/schemas/tsconfig.stspr.schema.json

exclude: false

# If not set, stspr derives a sensible default based on whether the file is
# canonical/standard/sibling. You can override explicitly:
#
# - true  => this tsconfig will include workspace dependency references
# - false => it won't
#
# includeWorkspaceDeps: true

references:
  add: []
  skip: []
```

## How references are computed (high-level)

For each package:

- **Standard tsconfig**: `tsconfig.json`
  - If `canonicalTsconfig.standardReferencesCanonical` is true and canonical is not `tsconfig.json`,
    stspr adds a reference from `tsconfig.json` → canonical.

- **Canonical tsconfig**: `canonicalTsconfig.path` (default `./tsconfig.json`)
  - If `canonicalTsconfig.includeSiblings` is true, stspr references sibling `tsconfig.*.json` files (excluding `tsconfig.json`)
  - If `canonicalTsconfig.includeWorkspaceDeps` is true, stspr adds workspace dependency references
  - Applies `stspr.package.yaml` `references.add/skip` (canonical only)
  - Applies matching `tsconfig.*.stspr.yaml` `references.add/skip`

- **Sibling tsconfigs**: other `tsconfig.*.json` files in the package directory
  - Optional `includeWorkspaceDeps` can be set per-tsconfig in `tsconfig.*.stspr.yaml`
  - Applies tsconfig-level `references.add/skip`

## JSON schemas (YAML IDE support)

This project ships JSON schemas for IDE autocomplete/validation.

### Schema URLs (unpkg)

Notes:

- The examples use `@latest` for convenience.
- If you prefer stability, replace `@latest` with a pinned version (typically the version you installed), e.g. `@0.1.1`.

- `stspr.root.yaml`: https://unpkg.com/@monorepo-tooling/sync-ts-project-refs@latest/schemas/stspr.root.schema.json
- `stspr.package.yaml`: https://unpkg.com/@monorepo-tooling/sync-ts-project-refs@latest/schemas/stspr.package.schema.json
- `tsconfig.*.stspr.yaml`: https://unpkg.com/@monorepo-tooling/sync-ts-project-refs@latest/schemas/tsconfig.stspr.schema.json

## Common recipes

### “Solution-style” pattern

Even though stspr does not have a dedicated `mode: solution`, the **solution-style pattern** is still fully supported.

The idea is:

- **`tsconfig.json` becomes a lightweight entry/discovery file**
  - It helps `tsserver` / editor tooling discover other `tsconfig.*.json` via `references`.
  - It typically does **not** carry the workspace dependency reference graph itself.
- **A non-standard canonical tsconfig becomes the real graph node**
  - Other packages (and the root solution tsconfig) reference **this** file.
  - This file typically **does** carry workspace dependency references.
  - It may also reference sibling `tsconfig.*.json` files for discovery.

#### Recommended configuration

1. Pick a canonical tsconfig that is not `tsconfig.json` (e.g. `tsconfig.canonical.json`):

```yaml
# stspr.package.yaml
canonicalTsconfig:
  path: ./tsconfig.canonical.json
  includeSiblings: true
  includeWorkspaceDeps: true
```

2. (Optional) Decide which sibling tsconfigs should also include workspace deps refs:

```yaml
# tsconfig.web.stspr.yaml
includeWorkspaceDeps: true
references:
  add: []
  skip: []
```

#### “Pure aggregator” variant (canonical does NOT include deps refs)

This is rarer, but sometimes useful if you want the canonical tsconfig to be a pure “discovery/aggregation” entry:

```yaml
# stspr.package.yaml
canonicalTsconfig:
  path: ./tsconfig.canonical.json
  includeSiblings: true
  includeWorkspaceDeps: false
```

Then explicitly enable deps refs on the tsconfig(s) that should carry them:

```yaml
# tsconfig.build.stspr.yaml
includeWorkspaceDeps: true
references:
  add: []
  skip: []
```

### Non-standard canonical tsconfig (e.g. `tsconfig.canonical.json`)

```yaml
# stspr.package.yaml
canonicalTsconfig:
  path: ./tsconfig.canonical.json
  includeSiblings: true
  includeWorkspaceDeps: true
```

### Exclude a package entirely

```yaml
# stspr.package.yaml
exclude: true
```

### Hard-exclude packages or generated tsconfigs globally

```yaml
# stspr.root.yaml
filters:
  excludePackages:
    - 'packages/legacy-*'
    - '!packages/legacy-keep'
  excludeTsconfigs:
    - '**/tsconfig.generated.json'
```

### Add/skip references only on the root solution tsconfig

```yaml
# stspr.root.yaml
rootSolution:
  references:
    add:
      - { path: './configs/tsconfig.shared.json' }
    skip:
      - { path: './packages/utils/tsconfig.json' }
```

## Troubleshooting

### “Why didn’t root tsconfig update?”

- If `rootSolution.tsconfigPath` does not exist, stspr **skips root updating** (by design).
- If packages are missing `tsconfig.json`, they are skipped.
- If a package matches `filters.excludePackages`, it is hard-excluded.

### “My editor doesn’t autocomplete YAML keys”

Ensure the YAML file includes the `$schema` comment and you have a YAML extension enabled:

- VS Code YAML extension: `redhat.vscode-yaml`