Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion ADAPTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,25 @@ what you took, not what you skipped. And the order matters more than the speed -
is written as a move that leaves the build green, because a migration that goes red in the
middle is a migration that gets reverted.

**Where to record the exception:** in this stack's own `stack.manifest.json`, in its
`exceptions` array - the file this table talks about. `self-verify.mjs` (core repo) merges
this stack's `files`/`sections`/`guards` and `exceptions` into the check it runs, so an
entry recorded here is honoured exactly like a core-manifest one:

```json
{ "kind": "file", "match": "pnpm-workspace.yaml", "reason": "npm workspace kept - no cooldown equivalent, see ADAPTING.md" }
```

`kind` is `"file"`, `"section"`, `"content"`, or `"key"`; `match` is the path exactly as
this stack's own entry above spells it (`file#heading` for a section, `file#key.path` for
a declared key). A guard script itself cannot be excepted by presence - that would delete
the check rather than waive it; a `kind: "content"` exception on a guard is allowed, since
the guard still runs and must pass.

| Entry | The repo probably has | The move |
|---|---|---|
| `biome.json` | ESLint + Prettier | Two-step, never big-bang: install Biome alongside, port rule intents (`biome migrate eslint --write` gets most), run both until the diff stabilizes, then remove ESLint/Prettier in their own PR. Keep Prettier only if SCSS stays (Biome does not format SCSS - that is the pick's own escape hatch). |
| `pnpm-workspace.yaml` | npm or yarn, maybe no workspace | `pnpm import` converts the existing lockfile; workspaces map 1:1. The policy block (release-age cooldown, allowed build scripts) is the point of the entry - merge it even if the workspace globs differ. On npm/yarn WITHOUT migration appetite: record the exception; the cooldown has no npm/yarn equivalent, name that trade-off. |
| `pnpm-workspace.yaml` | npm, yarn, bun, or Deno, maybe no workspace | `pnpm import` converts the existing lockfile where that source tool is actually supported - see the by-source-tool breakdown below, it is not all four. Workspaces map 1:1. The policy block (release-age cooldown, allowed build scripts) is the point of the entry - merge it even if the workspace globs differ. Without a migration path, or without migration appetite: record the exception (see above); the cooldown has no equivalent outside pnpm's own config, name that trade-off. |
| `tsconfig.base.json` | a looser tsconfig | Stage strictness: `strict` first, then `noUncheckedIndexedAccess` and friends one flag per PR - each flag surfaces real findings; a hundred errors at once teaches nothing. Extend, do not replace: their paths/aliases stay theirs. |
| `vitest.config.ts` | Jest, or tests mixed in one pile | Vitest reads most Jest suites as-is (`vitest run` first, fix the stragglers). The entry's real content is the tier split - unit vs integration as separate projects; introduce the naming convention (`*.test.ts` / `*.integration.test.ts`) before touching any test body. |
| `playwright.config.ts` | Cypress, or nothing | Nothing: start with the starter's two journeys (auth, home) adapted to their routes. Cypress: do not port wholesale - write the 3-5 journeys that matter in Playwright, retire Cypress when they cover the old suite's intent. |
Expand All @@ -47,6 +62,21 @@ middle is a migration that gets reverted.
| `.github/workflows/e2e.yml` | nothing, or e2e mixed into the main pipeline | Land it as its own workflow even if their e2e currently rides the fast gate: this tier boots a Docker stack and the app, so mixing it in makes every commit pay for a check most commits do not need. It is the one you gate a release on. |
| `.nvmrc` | none, or an older pin | Copy; if their runtime is older, the Node major bump is its own migration - route it through the modernize pass in the core's adoption guide, not through this phase. |

## `pnpm import`, by source package manager

The row above says `pnpm import` "converts the existing lockfile" as if that holds for
whatever the repo showed up with. It does not. Tested this week against two real
brownfield repos, both against this stack's pinned pnpm (11.1.2 - if a different `pnpm`
is the one actually on PATH, that mismatch is the first thing to check, see the yarn row):

| From | Command | What actually happens |
|---|---|---|
| npm (`package-lock.json`) | `pnpm import` | works - documented, supported input. |
| yarn classic (`yarn.lock` v1) | `pnpm import` | works - documented, supported input. |
| yarn Berry / yarn 4 (`yarn.lock` v2+) | drop `packageManager: "yarn@..."` from `package.json` first, then `pnpm import` | pnpm refuses to run at all ("This project is configured to use yarn") while that field still names yarn - dropping it is the adoption anyway, not a workaround. Once dropped, the dependency graph itself imports fine. Two real gaps, not blockers: a yarn `resolutions` override is silently dropped - `pnpm import` never reads that field, so re-create anything load-bearing as `pnpm.overrides` by hand and verify the pinned version still resolves. Older pnpm (7.x) crashed on a nested-path override selector (`pkg/**/pkg`, `ERR_PNPM_INVALID_OVERRIDE_SELECTOR`) migrating such an override; retested against 11.1.2 here and it no longer reproduces - if it still happens, the `pnpm` actually running is not the one this stack pins. |
| bun (`bun.lock` / `bun.lockb`) | no importer path - `pnpm import --help` lists only `package-lock.json` / `npm-shrinkwrap.json` / `yarn.lock` | fails immediately, `ERR_PNPM_LOCKFILE_NOT_FOUND` (reproduced). The real move: delete `bun.lock`/`bun.lockb`, run a fresh `pnpm install`. Every dependency re-resolves to whatever `package.json` allows rather than what bun had pinned - if the ranges there are not already exact (common outside this stack's `saveExact` policy, DECISIONS#8), diff the deleted `bun.lock` against the new `pnpm-lock.yaml` before merging and hand-repin anything that moved. |
| Deno (`deno.lock`) | no importer path, same supported-input list as bun | same real move as bun: delete `deno.lock`, fresh `pnpm install`, hand-repin from the deleted lockfile. Not tested against a live Deno repo this round (the two repos tested were bun and yarn) - treat this row as the same failure mode by inspection of `pnpm import`'s supported-input list, not as independently verified. |

## The app shell - picks, not table rows

The manifest's config entries migrate via the table above. The app-shell picks
Expand Down
12 changes: 12 additions & 0 deletions SHIPPED.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ number across both. These are what it runs here:
| Runs | Proves |
|---|---|
| `pnpm check:all` | format + types + lint green - the stack's own quality gate counted in the same drift number |
| the manifest's `requiredKeys` on `pnpm-workspace.yaml` | the supply-chain policy block (release-age cooldown, save-exact, no unreviewed lifecycle scripts) survived the merge, not just the file - the engine reads the three keys directly, no pnpm on PATH required |

Only `check:all` needs pnpm on PATH, and needs more than that to pass rather than fail
opaquely: this repo's dependencies already installed (`pnpm install` - the full tree, no
partial shortcut, network required at least once). It is a real toolchain run - format,
types, lint against the actual code - not something a config-file read could substitute
for, unlike the guard above it. Without pnpm on PATH the guard now says so explicitly
("pnpm not on PATH...") rather than failing with a bare shell `command not found` that
reads identically to a real lint failure in the drift report. Layer 1's own prerequisites
page (`docs/method/prerequisites.md` in the core repo) lists what every guard needs
generically (Node, git, bash, jq); this is this stack's addition to that same table, and
nothing here names it yet.

## What is not here

Expand Down
7 changes: 4 additions & 3 deletions stack.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"adapt": "merge",
"required": true,
"profile": "core",
"rule": "DECISIONS#8"
"rule": "DECISIONS#8",
"requiredKeys": ["minimumReleaseAge", "saveExact", "enablePrePostScripts"]
},
{
"path": "vitest.config.ts",
Expand Down Expand Up @@ -105,10 +106,10 @@
"guards": [
{
"id": "stack-check-all",
"run": "pnpm check:all",
"run": "command -v pnpm >/dev/null 2>&1 || { echo \"pnpm not on PATH - required for check:all (also needs pnpm install once, ~670MB/network - see SHIPPED.md, What gets checked)\" >&2; exit 1; }; pnpm check:all",
"kind": "static",
"blocks": true,
"purpose": "format + types + lint green - the stack's own quality gate counted in the same drift number",
"purpose": "format + types + lint green - the stack's own quality gate counted in the same drift number. Needs pnpm on PATH and dependencies already installed - a real toolchain run, not something a config-file read can substitute for; see SHIPPED.md for the prerequisite",
"profile": "core",
"rule": "DECISIONS#3"
}
Expand Down
Loading