diff --git a/ADAPTING.md b/ADAPTING.md index 9809336..9693460 100644 --- a/ADAPTING.md +++ b/ADAPTING.md @@ -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. | @@ -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 diff --git a/SHIPPED.md b/SHIPPED.md index 310bffe..8824c95 100644 --- a/SHIPPED.md +++ b/SHIPPED.md @@ -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 diff --git a/stack.manifest.json b/stack.manifest.json index ee1fc2b..eb111da 100644 --- a/stack.manifest.json +++ b/stack.manifest.json @@ -35,7 +35,8 @@ "adapt": "merge", "required": true, "profile": "core", - "rule": "DECISIONS#8" + "rule": "DECISIONS#8", + "requiredKeys": ["minimumReleaseAge", "saveExact", "enablePrePostScripts"] }, { "path": "vitest.config.ts", @@ -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" }