feat: check-git-install --check-links refuses local dependencies

- --check-links DIR... reports every link:, file:, workspace: or portal:
  dependency in each DIR/package.json and exits 1 (L4 CR-01)
- the tag mode runs the same check on the installed layer before building
- --self-test adds planted package.json cases next to the lockfile case
- README Releasing: swap the link for the seo tag pin, run --check-links,
  then the tag mode after pushing
This commit is contained in:
Jakub Zych
2026-11-04 22:54:18 +01:00
parent d64fe47adf
commit d4ba3e2ddf
2 changed files with 95 additions and 7 deletions

View File

@@ -280,10 +280,16 @@ To work on the layer inside an app, link it instead of installing from git: `pnp
A release is an annotated git tag (`v0.1.0`) on `main`, pushed to git.golem15.com. Apps pin it in `package.json` as `git+https://git.golem15.com/golem15/vue-journal-layer.git#<tag>`, and `pnpm-lock.yaml` records the tag's commit. To upgrade, change the tag and run `pnpm install`. The repository is public, so installing needs no credentials. The layer has no install-time build or `prepare` script, which a git-hosted dependency would need to run during `pnpm install`.
A content layer (this one, vue-menu-layer, vue-faq-layer) extends vue-seo-layer through its `vue-seo-layer` dependency, which points at the local checkout (`link:../vue-seo-layer`) during development. A tag cut with that link breaks every git install: the path resolves next to the pnpm store entry, not to a package. Before tagging any content layer:
1. Tag vue-seo-layer first, then in each content layer replace the link with the tag pin (`"vue-seo-layer": "git+https://git.golem15.com/golem15/vue-seo-layer.git#v0.2.0"`) and run `pnpm install`, so `pnpm-lock.yaml` records the seo layer's commit.
2. Run `bash scripts/check-git-install.sh --check-links ../vue-seo-layer ../vue-journal-layer ../vue-menu-layer ../vue-faq-layer`. It fails while any of those `package.json` files has a `link:`, `file:`, `workspace:` or `portal:` dependency, and names each one.
3. Tag and push, then run `bash scripts/check-git-install.sh <tag>`, which repeats the check on the installed tag before building.
## Testing
- `pnpm test` runs the unit tests in `tests/*.test.ts` with `node --test` (Node strips the TypeScript types; no test framework dependency). They cover the pure helpers in `app/utils/`: route normalization and path filling, the client against a fake fetcher (including the suggest query), the search helpers (term rules, diacritic-insensitive marking, plural forms, combobox keys, and the suggestion controller's debounce, abort and stale-answer handling under mocked timers), pagination, the HTML sanitizer (the Journal plugin's FormatHTML cases, the bypasses from its code review, and the Blog Editor corpus in `tests/fixtures/editor-corpus.json`, a byte copy of the plugin's), the H1 demotion and the body pipeline order, the featured image srcsets (`tests/srcset.test.ts`), the embed expansion and its sanitize-first order, the post page view, SEO values (fallback meta, the title element, ItemList), path-form routes, the old purge route's helpers and browser revalidation (`tests/siteCache.test.ts`), and pl/en key and placeholder parity. The shared purge route and the body link steps are tested in vue-seo-layer. They need no build, network or database.
- `scripts/check-git-install.sh <tag>` installs the layer from the git tag into a scratch app built from the layer's playground config (`.playground/nuxt.config.ts` extending the installed layer, and its `app.vue`), checks that `pnpm-lock.yaml` pins the tag's commit for vue-journal-layer (its own git dependencies, such as vue-seo-layer, are ignored), runs `nuxt build`, and runs the SSR smoke against the build. `--self-test` checks the lockfile reading only. Run it from a directory that is not inside a directory named `vue-journal-layer` (Nuxt treats such a path segment as the layer).
- `scripts/check-git-install.sh <tag>` installs the layer from the git tag into a scratch app built from the layer's playground config (`.playground/nuxt.config.ts` extending the installed layer, and its `app.vue`), checks that `pnpm-lock.yaml` pins the tag's commit for vue-journal-layer (its own git dependencies, such as vue-seo-layer, are ignored), checks that the installed `package.json` has no local dependency, runs `nuxt build`, and runs the SSR smoke against the build. `--check-links DIR...` checks the `package.json` of each directory for `link:`, `file:`, `workspace:` and `portal:` dependencies (the release guard, see [Releasing](#releasing)). `--self-test` checks the lockfile reading and the link check on planted files. Run it from a directory that is not inside a directory named `vue-journal-layer` (Nuxt treats such a path segment as the layer).
- `testing/smoke-ssr.mjs --output <.output> --fixtures <dir> --expect <file> [--env KEY=VALUE]...` boots a fixture Journal and SEO API and the built server and checks each line of the expectations file: `STATUS PATH [contains=TEXT] [notcontains=TEXT] [count=TEXT:N] [type=PREFIX] [header=NAME: VALUE] [noheader=NAME] [headings=strict] [cardlinks=N]`, every option repeatable, and `resolves PATH N` (the SEO resolve calls so far for a path, `*` for all). `headings=strict` wants exactly one `<h1` and no skipped level, `cardlinks=N` exactly N links in each post card. It also checks that SSR requests forwarded `x-forwarded-for` and that categories were requested with `include_empty=1`. `pnpm smoke:ssr` runs `expect-acme.txt` and the expectation files for `postTitleTag` forced to `h1` and `p`, the new-tab hint off and `journal.breadcrumbs` off.
- `testing/purge-check.mjs --output <.output> --fixtures <dir> --kind post|category --slug <slug> --path <page path>` proves the [site cache purge](#site-cache-purge) on a built server whose route rules cache pages: it caches the page, changes the entity in a temporary copy of the fixtures, requires the cached page to stay unchanged, purges with a random token, and requires the change on the next request. It also checks `max-age=0` on the page and its 304, the purged `_payload.json`, a second purge after re-caching, `401` without or with a wrong token, `404` behind a proxy and `404` on a server without a token. The last line is `purge-check: ok (<n> checks)`.
- `testing/purge-token-check.mjs --output <.output> --fixtures <dir> --path <page path>` proves on a built server that the purge token never reaches a page. It starts the server three times with fresh random tokens: under `NUXT_CACHE_PURGE_TOKEN`, under the deprecated `NUXT_SITE_CACHE_PURGE_TOKEN` with `NUXT_SITE_CACHE_ALLOW_PROXIED=true`, and under both names with different values. Each time `/`, the page and its `_payload.json` (when served) must contain no token value and neither `cachePurgeToken` nor `cacheAllowProxied`, the shared purge route must accept the expected bearer and refuse the others, and the deprecation warning must appear once with the old names only. The last line is `purge-token-check: ok (<n> checks)`.