Imported from flyocloud/nitro-astro (
AGENTS.md). Install upstream withnpx skills add flyocloud/nitro-astro. Copyright stays with the author.
AGENTS.md
npm workspace. lib/ is the published package @flyo/nitro-astro, playground/ is a local Astro app for trying it out.
Only lib/index.ts is bundled (build.lib.entry in lib/vite.config.ts). Everything else in the package — components, cdn.ts, middleware.ts, sitemap.ts, toolbar.ts — ships as raw source and is resolved by the consumer's own Astro/Vite toolchain. Keep that in mind: a file is not part of the package just because it exists.
Adding a component
-
lib/components/X.astro— the component. -
lib/components/X.ts— a two-line shim, same as every other one:import X from "./X.astro"; export default X;Required, not optional. TypeScript cannot resolve
.astroon its own, so thetypescondition must point at a.tsfile. -
lib/components/X.astro.d.ts— a fallback declaration for the line above:declare const X: (props: Record<string, any>) => any; export default X;Also required. Astro tooling resolves the real
X.astroand ignores this file, so prop types stay intact; plaintschas no Astro plugin and would otherwise report TS2307 inside the consumer'snode_modules— andskipLibCheckdoes not suppress it, because the shim is a.ts, not a.d.ts. -
Two entries in
exportsin lib/package.json — both spellings, and note the asymmetry (importon the short form goes to the shim, on the long form to the.astro):"./X.astro": { "types": "./components/X.ts", "import": "./components/X.ts", "require": "./components/X.ts" }, "./components/X.astro": { "types": "./components/X.ts", "import": "./components/X.astro", "require": "./components/X.astro" } -
The same two subpaths in
typesVersionsin lib/package.json, pointing at the shim:"X.astro": ["./components/X.ts"], "components/X.astro": ["./components/X.ts"]Also required — see the resolution note below.
-
npm test --workspace=lib.
Nothing else. files already ships all of components/, and index.ts deliberately does not re-export components — the exports map is the only way in. There is no wildcard in exports, so a subpath you forget to list is unreachable for consumers even though it is inside the tarball.
Adding a root-level module (like cdn.ts)
Add it to both files and exports in lib/package.json, as plain source:
"./thing.ts": "./thing.ts"
If it should instead be part of the bundle and the generated types, it has to be reachable from index.ts and listed in the dts include — see the dts notes below. cache.ts is such a module: nothing outside the bundle imports it, so it is in neither files nor exports.
What the tests enforce
lib/packaging.test.ts builds, runs npm pack --dry-run, and fails if:
- something
main/module/types/exports/typesVersionspoints at is missing from the tarball (forgot step 2 or thefilesentry), - test files, tsconfigs, tooling configs or stray
.d.tsfiles ship (components/*.astro.d.tsis the one allowed exception), dist/types/**contains anything the package does not reference,- a shipped source file imports a package that
dependenciesdoes not name (astroand the package's own name excepted — see the dependency note below), - an
.astrosubpath inexportshas notypesVersionsfallback, or the two disagree (forgot step 5), - a shim has no
components/X.astro.d.tsbeside it (forgot step 3), - a relative import inside
dist/types/**resolves to a file that is not shipped (see the declarations note below).
So a forgotten export or a junk file is a red CI run, not a broken release. If you change what the package ships, expect this suite to have an opinion.
Gotchas
dist/types/index.d.tsis the only declaration file, rolled up fromindex.tsand the siblings it re-exports (includeplusrollupTypeson the dts plugin in lib/vite.config.ts). Pointingtypesat some otherdist/types/*.d.tswill not work until you widen that.- A new module that
index.tsre-exports has to be added to the dtsinclude, next tocdn.tsandcache.ts. Without it api-extractor has nothing to inline and leavesfrom "./yours"in the rolled-up declarations — a path that resolves to nothing beside it. Nobody gets an error for that, because every Astro tsconfig setsskipLibCheck: the re-exported symbols just becomeanyin the consumer's editor.flyoImageUrlwasanythat way untilrollupTypeswas turned on. The packaging test now fails on it. exportsalone is not enough for consumers. TypeScript only readsexportsundermoduleResolutionbundler/node16/nodenext. A consumer whosetsconfig.jsondoes not extendastro/tsconfigs/*— or who has notsconfig.jsonat all — gets legacynode10resolution, whereexportsis invisible and@flyo/nitro-astro/BlockSlot.astrois looked up as a literal file at the package root. That file does not exist (components live incomponents/), so the IDE reportsTS2307: Cannot find module.typesVersionsis the fallback legacy resolution does read; it only affects types, runtime resolution goes throughexportseither way. Keep the two in sync — the tests do not let you forget..astroimports from.tsresolve viacomponents/X.astro.d.ts, not an ambientdeclare module "*.astro". The ambient version would have to be published to help consumers, and publishing it flattens the props of their components toany. lib/module.d.ts is forvirtual:*only.- Every bare import in shipped source must be a real
dependency. The bundle inlines its imports, sodist/works no matter whatdependenciessays — but the raw source is resolved from the consumer'snode_modules, where an undeclared package only resolves if something else in their tree happens to hoist it.camelcaseincomponents/FlyoNitroBlock.astrowas undeclared and resolved throughastro→boxen→camelcasefor exactly that reason, until an install that did not hoist it turned into a failed consumer build.astrois the one thing raw source may import freely: an integration is loaded from the consumer'sastro.config.mjs, so their Astro is always present and always the version that has to win. - Component lookup is keyed by lib/componentKey.ts on both ends. The virtual module
vite-plugin-flyo-components.tsemits registers eachcomponentskey undercomponentKey(name), andFlyoNitroBlock.astrolooks upcomponentKey(block.component). One implementation, two call sites, and they have to stay one: a second copy that drifts does not fail the build, it renders the fallback component for every block. That is also why the plugin emits a keyed registry instead of one named export per component — a Flyo name is not necessarily a valid JS identifier (2cols,hero/block), andexport { default as 2cols }is a syntax error in the emitted module. componentKey.tsis shipped raw and inlined in the bundle. The plugin imports it relatively (so rollup inlines it), the component imports@flyo/nitro-astro/componentKey(so the consumer resolves the shipped source). Both copies come from the one file, which is the point.vite-plugin-flyo-components.tsis about the consumer's components (src/components/flyo, exposed asvirtual:flyo-components). It has nothing to do with this library's own components — do not register anything there.- Live-edit code is an injected string in lib/index.ts, not a normal import: it is emitted into the consumer's page and resolved from their
node_modules. That is why bumping@flyo/nitro-js-bridgehere is not needed for clients to receive a new bridge — theirnpm updatepicks it up through the semver range. tsconfig.jsonexcludes./vite*.ts, which is why the vite plugin files get no declarations.
Docs
Three files, three audiences — keep them in sync when the public API changes:
- README.md — the documentation. Usage guide, configuration options, API and component reference. This is what users read on GitHub.
- lib/README.md — the npm package page. Stays short and links back to the repo; npm publishes it regardless of the
filesarray. - ai-instructions-astro.md — the integration advisory for AI coding agents working in a consumer project. Linked from the README.
- UPGRADE.md — breaking changes per release.
Commands
npm install
npm run build # builds lib
npm test --workspace=lib # unit + packaging tests (builds first)
npm run playground # localhost:4321
npx eslint
npx prettier . --write
Releases are semantic-release from main (see .github/workflows/release.yml); commit messages drive the version.