Skip to content

Commit df41c2b

Browse files
committed
feat(lint): update deps, build (tsdown), lint (oxlint, publint, tsdown)
1 parent 4d3bde2 commit df41c2b

File tree

8 files changed

+1291
-118
lines changed

8 files changed

+1291
-118
lines changed

conventions.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
### Formatter settings (from `biome.jsonc`)
2+
3+
- **Indent style:** Tabs (not spaces) for code files.
4+
- **Indent width:** 2.
5+
- **Line width:** 100 characters.
6+
- **Trailing commas:** `all` — always include trailing commas.
7+
- **Quote style:** Single quotes (`'`).
8+
- **Semicolons:** Always.
9+
- **Expand:** `auto` — Biome auto-decides when to expand arrays/objects.
10+
11+
---
12+
13+
## 2. Import Conventions
14+
15+
### Always use the `node:` protocol for Node.js builtins
16+
17+
Enforced as an **error** via Biome's `useNodejsImportProtocol` rule. Every import of a Node built-in must use the `node:` prefix:
18+
19+
```ts
20+
// ✅ Correct
21+
import fs from "node:fs";
22+
import path from "node:path";
23+
24+
// ❌ Wrong — will fail lint
25+
import fs from "fs";
26+
import path from "path";
27+
```
28+
29+
### Enforce `import type` for type-only imports
30+
31+
Enforced as an **error** via Biome's `useImportType` rule. If an import is only used as a type (not at runtime), it must use `import type`:
32+
33+
```ts
34+
// ✅ Correct
35+
import type { AstroConfig } from "../types/public/config.js";
36+
import { getFileInfo } from "../vite-plugin-utils/index.js";
37+
38+
// ❌ Wrong — pulls runtime code for a type-only import
39+
import { AstroConfig } from "../types/public/config.js";
40+
```
41+
42+
### Import organization
43+
44+
Biome's `organizeImports` assist is set to `"on"`, which auto-sorts and groups imports.
45+
46+
---
47+
48+
## 3. Restricted Use of Node.js APIs
49+
50+
This is a foundational architectural constraint. Astro code may run in non-Node environments like **Bun** or **Cloudflare Workers**, so Node.js API usage is strictly limited to specific areas of the codebase.
51+
52+
### Rules
53+
54+
- **Runtime-agnostic code** (code that shouldn't use Node.js APIs) should be placed inside folders or files called `runtime` (`runtime/` or `runtime.ts`).
55+
- **Vite plugin implementations** may use Node.js APIs, but if a Vite plugin returns a **virtual module**, that virtual module cannot use Node.js APIs.
56+
- Prefer **`URL` and file URL APIs** (`new URL(...)`, `import.meta.url`) over `node:path` string manipulation where possible. The codebase favors URL-based path handling for cross-runtime compatibility.
57+
58+
---
59+
60+
## 4. Function Signature Conventions
61+
62+
### Options bag pattern
63+
64+
Functions with more than two parameters are collapsed into an **options object**. This is a pervasive pattern throughout the codebase:
65+
66+
```ts
67+
// ✅ Correct — options bag with a typed interface
68+
interface CompileAstroOption {
69+
compileProps: CompileProps;
70+
astroFileToCompileMetadata: Map<string, CompileMetadata>;
71+
logger: Logger;
72+
}
73+
74+
export async function compileAstro({
75+
compileProps,
76+
astroFileToCompileMetadata,
77+
logger,
78+
}: CompileAstroOption): Promise<CompileAstroResult> { ... }
79+
```
80+
81+
```ts
82+
// ✅ Also correct — inline destructuring for simpler cases
83+
export async function generateHydrateScript(
84+
scriptOptions: HydrateScriptOptions,
85+
metadata: Required<AstroComponentMetadata>,
86+
): Promise<SSRElement> { ... }
87+
```
88+
89+
### Public APIs should default to `async`
90+
91+
Public-facing functions default to being `async`, even if they don't strictly need to be today. This future-proofs the API against needing to introduce async operations later without a breaking change.
92+
93+
---
94+
95+
## 5. Error Handling Convention
96+
97+
Astro has a **unified, structured error system**. All errors go through a central `AstroError` class and are defined in a single data file.
98+
99+
### Never throw generic `Error` — use `AstroError`
100+
101+
```ts
102+
// ❌ Wrong
103+
throw new Error("Something went wrong");
104+
105+
// ✅ Correct
106+
throw new AstroError({
107+
...AstroErrorData.NoMatchingImport,
108+
message: AstroErrorData.NoMatchingImport.message(metadata.displayName),
109+
});
110+
```
111+
112+
### Error definitions live in `errors-data.ts`
113+
114+
All error data is centralized in `packages/astro/src/core/errors/errors-data.ts`. Each error follows a strict shape:
115+
116+
- **`name`** — A permanent, unique PascalCase identifier (never changed once published).
117+
- **`title`** — A brief user-facing summary.
118+
- **`message`** — Can be a static string or a function for dynamic context. Describes what happened and what action to take.
119+
- **`hint`** — Optional. Additional context, links to docs, or common causes.
120+
121+
### Error writing guidelines (from `errors/README.md`)
122+
123+
- Write from the **user's perspective**, not Astro internals ("You are missing..." not "Astro cannot find...").
124+
- **Avoid cutesy language** — no "Oops!" The developer may be frustrated.
125+
- Keep messages **skimmable** — short, clear sentences.
126+
- Don't prefix with "Error:" or "Hint:" — the UI already adds labels.
127+
- **Names are permanent** — never rename, so users can always search for them.
128+
- Dynamic messages use a function shape: `message: (param) => \`...\``.
129+
- Avoid complex logic inside error definitions.
130+
- Errors are **grouped by domain** (Content Collections, Images, Routing, etc.) and display on the error reference page in definition order.
131+
132+
---
133+
134+
## 6. Code Architecture Principles
135+
136+
### Decouple business logic from infrastructure
137+
138+
The CONTRIBUTING guide explicitly calls this out as a core principle:
139+
140+
- **Infrastructure:** Code that depends on external systems (DB calls, file system, randomness, etc.) or requires a special environment to run.
141+
- **Business logic (domain/core):** Pure logic that's easy to run from anywhere.
142+
143+
In practice this means: avoid side effects, make external dependencies explicit, and **pass more things as arguments** (dependency injection style).
144+
145+
### Test all implementations
146+
147+
If an infrastructure implementation is just a thin wrapper around an npm package, you may skip testing it and trust the package's own tests. But all business logic should be tested.
148+
149+
---
150+
151+
## 8. Code Cleanliness Rules
152+
153+
All enforced at the **error** level:
154+
155+
| Rule | Effect |
156+
| ---------------------------- | ----------------------------------------------------- |
157+
| `noUnusedVariables` | No unused variables (with `ignoreRestSiblings: true`) |
158+
| `noUnusedFunctionParameters` | No unused function parameters |
159+
| `noUnusedImports` | No unused imports |
160+
| `useNodejsImportProtocol` | Must use `node:` prefix for Node builtins |
161+
| `useImportType` | Must use `import type` for type-only imports |
162+
163+
- Node.js engine requirement: `>=22.12.0`.
164+
- Package manager: `pnpm` (enforced via `only-allow`).
165+
166+
---
167+
168+
## 12. Code Style
169+
170+
The observed patterns from the source code include:
171+
172+
- **Explicit return types** on exported functions.
173+
- **Interface-first design** — define an interface for options bags, then destructure in the function signature.
174+
- **JSDoc comments** on exported functions and complex internal functions, using `/** */` style. Avoid excessive or self-documenting comments.
175+
- **Explicit `.ts` extensions in import paths** — even for TypeScript files, imports use literal extensions.
176+
- **Avoid barrel files via `index.ts`** — packages organize re-exports through index files.
177+
- **`const` by default**`let` only when reassignment is needed.
178+
179+
---
180+
181+
## Summary of Key Rules
182+
183+
| Convention | Details |
184+
| --------------- | ------------------------------------------------------------------------------------------ |
185+
| Formatter | Biome: tabs, single quotes, semicolons always, trailing commas always, 100-char line width |
186+
| Node builtins | Always `node:` prefix (error-level lint rule) |
187+
| Type imports | Always `import type` for type-only (error-level lint rule) |
188+
| Node.js APIs | Restricted to non-`runtime/` code; virtual modules must be runtime-agnostic |
189+
| Function params | >2 params → options bag with a typed interface |
190+
| Public APIs | Default to `async` |
191+
| Error handling | Always `AstroError` with centralized error data; never generic `Error` |
192+
| `console.log` | Warned; use `console.info`/`warn`/`error`/`debug` instead |
193+
| Unused code | Unused vars, params, and imports are errors |
194+
| Path handling | Prefer `URL` / file URLs over `node:path` string manipulation |
195+
| Testing | Vitest via `astro-scripts`; `bgproc` for dev servers; `agent-browser` for HMR |
196+
| Scripting | `node -e`, not Python |
197+
| Business logic | Decouple from infrastructure; make dependencies explicit via arguments |

oxlintrc.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@
3131
}
3232
],
3333
"import/no-commonjs": "error",
34-
"node/no-path-concat": "error"
34+
"node/no-path-concat": "error",
35+
36+
"unicorn/prefer-node-protocol": "error",
37+
"typescript/consistent-type-imports": "error",
38+
"no-console": ["warn", { "allow": ["info", "warn", "error", "debug"] }],
39+
"prefer-const": "error",
40+
"no-var": "error",
41+
"max-params": ["error", { "max": 2 }],
42+
"typescript/explicit-function-return-type": ["warn", { "allowExpressions": true }],
43+
44+
"import/no-default-export": "error",
45+
"bombshell-dev/no-generic-error": "error",
46+
"bombshell-dev/require-export-jsdoc": "warn",
47+
"bombshell-dev/exported-function-async": "warn"
3548
}
3649
}

package.json

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,79 @@
11
{
2-
"name": "@bomb.sh/tools",
3-
"version": "0.1.0",
4-
"type": "module",
5-
"license": "MIT",
6-
"bin": {
7-
"bsh": "dist/bin.mjs"
2+
"name": "@bomb.sh/tools",
3+
"version": "0.1.0",
4+
"description": "The internal dev, build, and lint CLI for Bombshell projects",
5+
"keywords": [
6+
"bombshell",
7+
"cli",
8+
"internal"
9+
],
10+
"homepage": "https://bomb.sh",
11+
"license": "MIT",
12+
"author": {
13+
"name": "Bombshell",
14+
"email": "oss@bomb.sh",
15+
"url": "https://bomb.sh"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "git+https://github.com/bombshell-dev/tools.git"
20+
},
21+
"bin": {
22+
"bsh": "dist/bin.mjs"
23+
},
24+
"type": "module",
25+
"exports": {
26+
".": {
27+
"import": "./dist/bin.mjs"
828
},
9-
"description": "The internal dev, build, and lint CLI for Bombshell projects",
10-
"keywords": [
11-
"cli",
12-
"bombshell",
13-
"internal"
29+
"./*": "./dist/*",
30+
"./package.json": "./package.json",
31+
"./tsconfig.json": "./tsconfig.json"
32+
},
33+
"publishConfig": {
34+
"access": "public"
35+
},
36+
"scripts": {
37+
"bsh": "node --experimental-strip-types --no-warnings ./src/bin.ts",
38+
"dev": "pnpm run bsh dev",
39+
"build": "pnpm run bsh build",
40+
"format": "pnpm run bsh format",
41+
"init": "pnpm run bsh init",
42+
"lint": "pnpm run bsh lint",
43+
"test": "pnpm run bsh test"
44+
},
45+
"dependencies": {
46+
"@bomb.sh/args": "^0.3.1",
47+
"@typescript/native-preview": "7.0.0-dev.20260307.1",
48+
"knip": "^5.85.0",
49+
"oxfmt": "^0.36.0",
50+
"oxlint": "^1.51.0",
51+
"publint": "^0.3.18",
52+
"tinyexec": "^1.0.1",
53+
"tsdown": "^0.21.0-beta.2",
54+
"vitest": "^4.0.18"
55+
},
56+
"devDependencies": {
57+
"@changesets/cli": "^2.28.1",
58+
"@types/node": "^22.13.14"
59+
},
60+
"volta": {
61+
"node": "22.14.0"
62+
},
63+
"packageManager": "pnpm@10.4.0",
64+
"pnpm": {},
65+
"knip": {
66+
"ignore": [
67+
"rules/**"
1468
],
15-
"homepage": "https://bomb.sh",
16-
"author": {
17-
"name": "Bombshell",
18-
"email": "oss@bomb.sh",
19-
"url": "https://bomb.sh"
20-
},
21-
"repository": {
22-
"type": "git",
23-
"url": "git+https://github.com/bombshell-dev/tools.git"
24-
},
25-
"publishConfig": {
26-
"access": "public"
27-
},
28-
"exports": {
29-
".": {
30-
"import": "./dist/bin.mjs"
31-
},
32-
"./*": "./dist/*",
33-
"./package.json": "./package.json",
34-
"./tsconfig.json": "./tsconfig.json"
35-
},
36-
"scripts": {
37-
"bsh": "node --experimental-strip-types --no-warnings ./src/bin.ts",
38-
"dev": "pnpm run bsh dev",
39-
"build": "pnpm run bsh build",
40-
"format": "pnpm run bsh format",
41-
"init": "pnpm run bsh init",
42-
"lint": "pnpm run bsh lint",
43-
"test": "pnpm run bsh test"
44-
},
45-
"devDependencies": {
46-
"@changesets/cli": "^2.28.1",
47-
"@types/node": "^22.13.14"
48-
},
49-
"dependencies": {
50-
"@bomb.sh/args": "^0.3.1",
51-
"escalade": "^3.2.0",
52-
"oxfmt": "^0.36.0",
53-
"oxlint": "^1.51.0",
54-
"publint": "^0.3.18",
55-
"tinyexec": "^1.0.1",
56-
"tsdown": "^0.21.0-beta.2",
57-
"vitest": "^4.0.18"
58-
},
59-
"packageManager": "pnpm@10.4.0",
60-
"volta": {
61-
"node": "22.14.0"
62-
},
63-
"pnpm": {}
69+
"ignoreDependencies": [
70+
"@bomb.sh/args",
71+
"@typescript/native-preview",
72+
"oxfmt",
73+
"oxlint",
74+
"publint",
75+
"tsdown",
76+
"vitest"
77+
]
78+
}
6479
}

0 commit comments

Comments
 (0)