Maintenance Commands
These are checks for the repository itself, not for apps that use the module. They all live in the @i18n-micro/scripts workspace package and run through one CLI:
pnpm -C scripts cli <command> --helpEach one exists because something drifted silently: the code still built, every test still passed, and the problem only surfaced later — in a review, in a bug report, or after publishing. They are the checks with no other owner.
| Command | Catches |
|---|---|
payload-budget | the translation dictionary creeping back into the HTML |
docs-audit | options and pages the documentation no longer matches |
api-surface | an export removed or retyped without anyone noticing |
deps-audit | versions escaping the catalog, packages imported but never declared |
fixtures-audit | test fixtures nothing references |
ensure-release-source | root version / CHANGELOG edited outside cli release |
preflight | the rest of them, before a release |
Two of them do double duty: the artifacts api-surface and payload-budget produce are also what the Package APIs and Performance pages read at build time, so the reference documentation and the release gates cannot disagree — they are the same files. See Documentation from data.
Documentation from code
Reference pages are generated as plain Markdown, so the site, the llms.txt bundle and anything else reading the sources all get the same content:
pnpm run docs:generate # write the reference
pnpm run docs:generate:check # fail when a page would change — what CI runspnpm run docs:dev and docs:build run it first, so the site is never built from stale pages.
| What is generated | Read from | Using |
|---|---|---|
| Component props, slots and events | src/runtime/components/*.vue | vue-docgen-api |
| Composable and helper reference | src/runtime/composables/*.ts, PluginsInjections | TypeDoc |
| Module options | the ModuleOptions type | the TypeScript AST |
| Package APIs | scripts/api-surface/*.api.txt | the snapshots api-surface checks |
| Payload numbers in Performance | scripts/payload-budget.json | the budget payload-budget enforces |
Whole pages (one per package) are written outright; everything else replaces a marked region inside a hand-written page, so the prose and examples around it stay under an author's control:
<!-- generated:methods-index — do not edit; run `pnpm run docs:generate` -->
<!-- /generated:methods-index -->Repeated content links instead of repeating: a member list already documented on another package's page becomes a one-line reference to it. That alone removed 17 KB from the package reference.
Writing the documentation in the code
The prose lives next to what it describes, so a rename cannot leave it behind:
/** Translate with pluralization. `params` may be the count itself, or an object containing `count`. */
$tc: (key: string, params: number | Params, defaultValue?: string) => string<!-- @slot Content before the button that opens the dropdown. -->
<slot name="before-button" />A <script setup> component is described through defineOptions, the form vue-docgen-api reads:
/** A link that keeps the active locale. */
defineOptions({ name: 'I18nLink' })Two tests keep this honest: every prop and slot must carry a comment, and every injected helper and composable must carry a description. Adding one without documenting it fails the suite rather than producing a blank table cell.
For LLMs
vitepress-plugin-llms publishes /llms.txt (an index) and /llms-full.txt (one bundle), plus a .md twin of every page. Because the reference is real Markdown rather than a component rendering JSON in the browser, all of it reaches those files — the option table, the helper signatures and the component props included.
payload-budget
The SSR payload carries the translation chunks a page loaded, so its size follows what the app asks for. Nothing else notices when that changes: a payload that doubled renders correctly and passes every test, it is just heavier.
pnpm run budget:payload # build the playground and compare
pnpm -C scripts cli payload-budget --skip-build # reuse an existing .output
pnpm run budget:payload:update # record the current numbers as the budgetThe budget lives in scripts/payload-budget.json and is committed, so a change to it is a change a reviewer sees:
{
"app": "playground",
"routes": ["/", "/de"],
"limits": { "maxNuxtData": 7996113, "totalNuxtData": 15984808, "clientAssets": 506360 }
}The playground is the default target because its dictionary is deliberately huge — around 15 MB on disk — so a change in what the payload carries is unmissable rather than marginal. Those numbers are a fixed point to compare against, not what a real application should expect; no real dictionary is that size.
--update writes the measured numbers plus --tolerance percent of headroom (10% by default), so ordinary growth does not trip the check while a step change cannot hide.
docs-audit
Four kinds of drift, none of which the VitePress build notices:
- an option exists in
ModuleOptionsbut appears nowhere indocs/ - a nav or sidebar entry points at a page that was renamed or removed
- a page links to another page that no longer exists
- a page is not reachable from the nav or the sidebar at all (warning)
Dynamic routes are understood: a [param].md template is not itself expected to be linked, and a link to a page that template renders (/api/packages/core) resolves even though no such file exists.
pnpm run audit:docs
pnpm -C scripts cli docs-audit --warnings-as-errorsOptions are read from the TypeScript AST of packages/types/src/index.ts rather than by pattern-matching the file, because the interface carries JSDoc prose and nested object types that a regex reports as members that do not exist.
Sidebar links are resolved the way VitePress resolves them, including the base a group declares and the cleanUrls behaviour where /news may be served by either news.md or news/index.md.
api-surface
Removing an export or changing its type breaks consumers, and nothing in the repository notices: the package builds, its tests pass, and it publishes. This turns that into a diff, and into a list you can paste into the release notes.
pnpm run api:surface # compare against the committed snapshots
pnpm run api:surface:update # accept the current surface
pnpm -C scripts cli api-surface --package coreSnapshots live in scripts/api-surface/*.api.txt, one file per package, one line per export or member:
# . (src/index.ts)
class BaseI18n
member BaseI18n.clearCache: () => void
member BaseI18n.t: (key: string, params?: Params, …) => CleanTranslationA line per member rather than per symbol is deliberate: a class with thirty methods on one line turns "renamed one method" into a diff nobody can read, and TypeScript truncates long shapes to ... 7 more ..., which would make the snapshot lossy as well.
The surface is read from src through the TypeScript program, so no build is needed and a stale dist cannot hide a change. Removals and signature changes fail the command; additions are reported but do not.
deps-audit
Two failures that otherwise surface late and confusingly.
A version escaping the catalog. A dependency pinned in a package.json while pnpm-workspace.yaml moves on — the package keeps building against a version nothing else in the workspace uses.
A package imported but never declared. unbuild only reports this as Implicitly bundling X at pack time, long after the change that caused it. The audit distinguishes three cases, because they are not equally wrong:
| Finding | Level | Meaning |
|---|---|---|
undeclared-import | error | imported at runtime, declared nowhere |
dev-only-import | warning | only a devDependency — correct if the build inlines it, wrong otherwise |
undeclared-type-import | warning | import type only; the emitted .d.ts references a package consumers may not have |
pnpm run audit:deps
pnpm -C scripts cli deps-audit --warnings-as-errorsIt also reports catalog entries nothing uses, and peerDependencies with no matching devDependency — a peer the package cannot resolve locally cannot be typechecked or tested here.
Sources are scanned with comments removed and template literals emptied. Both matter: a sentence ending … from "no request context" matches a naive import pattern, and types-generator writes a real import '@i18n-micro/types'; into the .d.ts it generates — which is that file's dependency, not the generator's.
fixtures-audit
Every fixture is a full Nuxt app, so one that outlived the test it was written for is invisible: it just makes the suite slower and every config change bigger.
pnpm run audit:fixtures
pnpm -C scripts cli fixtures-audit --strictIt reports and never deletes. A fixture name can be composed at runtime, so anything listed is a candidate to check, not a verdict. References are searched across test/, .github/, package.json and the vitest configs, since a fixture is often named from a config rather than from a test file.
It also flags build output (.nuxt, .output, .output-shared, .nuxt-test, test-results) inside a fixture. pnpm run clean:test removes most of it; .output it does not, so remove that one by hand.
--strict fails only on build output that is committed: a local .nuxt is gitignored and expected after running the suites, and an unreferenced fixture is a candidate to check rather than a defect — which is why the command never deletes either.
ensure-release-source
Root package.json version and CHANGELOG.md must only move through pnpm -C scripts cli release (changelogen). Hand-bumps and hand-edited changelogs look fine until the next release rewrites them or publishes the wrong version.
pnpm run release:source
pnpm -C scripts cli ensure-release-source
pnpm -C scripts cli ensure-release-source --base v3.24.0Checks:
- uncommitted edits to
CHANGELOG.mdor a dirty rootpackage.json - commits since the previous release tag that change
CHANGELOG.mdwithout achore(release): vX.Y.Zsubject - commits that bump the root version the same way
Wired after release:auth in release:patch / minor / major, and into release:check / preflight.
preflight
The release scripts chain the gates with &&, which stops at the first failure: a run that fails on the first gate tells you nothing about the other five, and each fix-and-retry runs the whole chain again. preflight runs them all and reports together, publishing nothing.
pnpm run preflight
pnpm -C scripts cli preflight --npm # also check the registry and npm auth
pnpm -C scripts cli preflight --offline # skip everything needing the network
pnpm -C scripts cli preflight --budget # also build the app and check the payload budgetpayload-budget is behind --budget because it builds an application: a gate that takes ten minutes by default is a gate people stop running.
Release preflight
ok deps-audit
ok verify-packages
ok api-surface
ok docs-audit
ok check-versions
ok ensure-release-source
- ensure-npm-auth skipped (needs --npm)
Would publish 15 package(s) (changelog from v3.21.4):
…
All gates passed. Safe to release.In CI
payload-budget builds an app, so it belongs on pull requests that touch the runtime rather than on every push. The rest are seconds each and are cheap enough to run always.
See also Release Smoke Checks, which verifies the published package by installing it into a real app.