withastro/astro
Patterns and conventions
The Astro repo has been around since March 2021 and has accumulated a small number of strong conventions that are worth knowing before you write code.
Source of truth: AGENTS.md and CONTRIBUTING.md
AGENTS.md (root) is the most up-to-date pointer for AI agents and humans alike, including monorepo layout, test commands, the bgproc/agent-browser helpers, and the optimize-deps deep dive. CONTRIBUTING.md (root) covers local setup, the changeset workflow, the priority labels (p1–p5), the issue triage flowchart, and snapshot/prerelease publishing. Read both before opening a PR.
Style is enforced, not documented
There is no manual style guide for TypeScript code. From STYLE_GUIDE.md:
Anything enforced by linting and formatting is considered a style rule. It is strictly required that you follow all style rules.
Concretely:
- Biome is the canonical linter and formatter. Config:
biome.jsonc. Runpnpm run lint:fixto auto-fix. - Prettier handles non-code files (Markdown, JSON, etc.). Config:
prettier.config.mjs. Runpnpm run format. - ESLint (
eslint.config.js) supplies a small set of project-specific rules on top of Biome (mostly around regex safety and TypeScript-eslint). - knip (
knip.js) detects unused exports duringpnpm run lint. - The
.git-blame-ignore-revsfile lists formatting commits to ignore ingit blame.
Runtime / non-runtime split
The use of
Node.jsAPIs (e.g.node:) is limited and should be done only in specific parts of the code. Code that is runtime-agnostic should be placed inside folders or files calledruntime(runtime/orruntime.ts). —CONTRIBUTING.md
This applies to packages/astro/src/. node: imports are allowed in:
packages/astro/src/core/(Node-only)packages/astro/src/cli/- Vite plugin implementations (the plugin file itself, not the virtual modules it returns)
They are forbidden in:
packages/astro/src/runtime/server/andpackages/astro/src/runtime/client/- Files literally named
runtime.tsor directories calledruntime/ - Virtual modules emitted by Vite plugins
If you import a Node-only helper from runtime code, the build either fails on Cloudflare Workers or Vercel Edge at runtime.
Public vs. internal exports
packages/astro/package.json declares two export maps:
"exports"— the monorepo view, with./_internal/*subpaths used by sibling packages."publishConfig.exports"— the npm view, which pnpm rewrites to"exports"at publish time. Internal subpaths never ship.
When you add an export:
| Visibility | "exports" |
"publishConfig.exports" |
|---|---|---|
| Public | Yes | Yes |
| Internal | Yes (under ./_internal/) |
No |
packages/astro/test/exports.test.ts enforces that the two stay in sync.
When importing from another workspace package, prefer the named subpath over a deep relative path:
// Good
import { loadFixture } from 'astro/_internal/test/test-utils';
// Bad
import { loadFixture } from '../../../astro/test/test-utils.js';Decoupling business logic from infrastructure
CONTRIBUTING.md documents this pattern at length, with a worked createKey example. The shape:
- Infrastructure: external systems (filesystem, network, crypto, time, terminal).
- Business logic: pure logic.
Make external dependencies explicit by passing them as parameters or via abstract interfaces. The cli/ module is the most consistent example — every concrete dependency has an Infra*Provider class and a matching interface in cli/definitions.ts. See for example packages/astro/src/cli/info/infra/cli-debug-info-provider.ts paired with packages/astro/src/cli/info/core/info.ts. Tests then pass SpyLogger, FakeKeyGenerator, etc.
This is the convention "to follow when writing new code". Older code does not always follow it; do not refactor unprompted.
TypeScript boundaries
- TypeScript
5.9.x,module: NodeNext,moduleResolution: NodeNext. Seetsconfig.base.json. - Type-only imports use
import type { … }. Biome'suseImportTyperule enforces this. - Centralized types live in
packages/astro/src/types/, split intointernal/andpublic/. Public types must be re-exported fromastroto be considered API. - Zod is used for runtime validation of user config and content schemas. Astro re-exports its bundled Zod via
astro/zodfor parity with user-facing APIs (zod/v4is what the schemas are written against).
Errors
Astro errors are defined declaratively in packages/astro/src/core/errors/errors-data.ts (the second-largest single file in the repo at 2,261 lines). Each error has a code, a title, a message function, and a hint. Throwing should use AstroError from core/errors/errors.ts. There is also a separate userError.ts exposed via astro/errors for integrations.
The dev overlay error display lives in packages/astro/src/core/errors/dev/ and the Vite plugin in packages/astro/src/vite-plugin-overlay/.
Logging
packages/astro/src/core/logger/ defines the abstract logger. Concrete implementations (node, console, JSON) live in core/logger/impls/. The CLI exposes its own pretty implementations via cli/infra/. Public logger entrypoints are exported as astro/logger, astro/logger/node, etc.
SKIP_FORMAT is a sentinel label that bypasses the logger's prefix formatting. Use it sparingly.
Telemetry
Astro telemetry is opt-out and runs through packages/telemetry/. Events are defined in packages/astro/src/events/index.ts. The CLI calls notify() and update() to surface and toggle telemetry, gated by astro telemetry enable|disable|reset. Never log PII.
Changesets
Be sure to add a changeset when something has changed with Astro. Non-packages (
examples/*) do not need changesets. —CONTRIBUTING.md
Use pnpm exec changeset and pick patch, minor, or major. The release PR aggregates changesets — see lore for how releases work.
Naming & file conventions
- Filenames are
kebab-case.ts(with notable exceptions for class-only files such asRenderContextlowercased torender-context.ts). - Class names are
PascalCase. - Test files end in
.test.js(mocha) or.test.ts(newernode:testpackages, seepackages/astro/scriptsandpnpm-workspace.yaml). - Vite plugin functions are named
vitePluginX(...)and live invite-plugin-x.tsorvite-plugin-x/index.ts. - The
_internalprefix is reserved for un-published export subpaths. astro:is reserved for virtual module names.
Related pages
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.