forked from github/quartz
243 lines
7.3 KiB
Markdown
243 lines
7.3 KiB
Markdown
# AGENTS.md - Coding Agent Instructions
|
|
|
|
This document provides essential information for AI coding agents working in this repository.
|
|
|
|
## Project Overview
|
|
|
|
**Quartz** is a static site generator for publishing digital gardens and notes as websites.
|
|
Built with TypeScript, Preact, and unified/remark/rehype for markdown processing.
|
|
|
|
| Stack | Technology |
|
|
| ------------- | ----------------------------------------- |
|
|
| Language | TypeScript 5.x (strict mode) |
|
|
| Runtime | Node.js >=22 (v22.16.0 pinned) |
|
|
| Package Mgr | npm >=10.9.2 |
|
|
| Module System | ES Modules (`"type": "module"`) |
|
|
| UI Framework | Preact 10.x (JSX with `react-jsx` pragma) |
|
|
| Build Tool | esbuild |
|
|
| Styling | SCSS via esbuild-sass-plugin |
|
|
|
|
## Build, Lint, and Test Commands
|
|
|
|
```bash
|
|
# Type check and format check (CI validation)
|
|
npm run check
|
|
|
|
# Auto-format code with Prettier
|
|
npm run format
|
|
|
|
# Run all tests
|
|
npm run test
|
|
|
|
# Run a single test file
|
|
npx tsx --test quartz/util/path.test.ts
|
|
|
|
# Run tests matching a pattern (use --test-name-pattern)
|
|
npx tsx --test --test-name-pattern="typeguards" quartz/util/path.test.ts
|
|
|
|
# Build the static site
|
|
npx quartz build
|
|
|
|
# Build and serve with hot reload
|
|
npx quartz build --serve
|
|
|
|
# Profile build performance
|
|
npm run profile
|
|
```
|
|
|
|
### Test Files Location
|
|
|
|
Tests use Node.js native test runner via `tsx`. Test files follow the `*.test.ts` pattern:
|
|
|
|
- `quartz/util/path.test.ts`
|
|
- `quartz/util/fileTrie.test.ts`
|
|
- `quartz/components/scripts/search.test.ts`
|
|
|
|
## Code Style Guidelines
|
|
|
|
### Prettier Configuration (`.prettierrc`)
|
|
|
|
```json
|
|
{
|
|
"printWidth": 100,
|
|
"tabWidth": 2,
|
|
"semi": false,
|
|
"trailingComma": "all",
|
|
"quoteProps": "as-needed"
|
|
}
|
|
```
|
|
|
|
**No ESLint** - only Prettier for formatting. Run `npm run format` before committing.
|
|
|
|
### TypeScript Configuration
|
|
|
|
- **Strict mode enabled** (`strict: true`)
|
|
- `noUnusedLocals: true` - no unused variables
|
|
- `noUnusedParameters: true` - no unused function parameters
|
|
- JSX configured for Preact (`jsxImportSource: "preact"`)
|
|
|
|
### Import Conventions
|
|
|
|
```typescript
|
|
// 1. External packages first
|
|
import { PluggableList } from "unified"
|
|
import { visit } from "unist-util-visit"
|
|
|
|
// 2. Internal utilities/types (relative paths)
|
|
import { QuartzTransformerPlugin } from "../types"
|
|
import { FilePath, slugifyFilePath } from "../../util/path"
|
|
import { i18n } from "../../i18n"
|
|
```
|
|
|
|
### Naming Conventions
|
|
|
|
| Element | Convention | Example |
|
|
| ---------------- | ------------ | ----------------------------------- |
|
|
| Files (utils) | camelCase | `path.ts`, `fileTrie.ts` |
|
|
| Files (comps) | PascalCase | `TableOfContents.tsx`, `Search.tsx` |
|
|
| Types/Interfaces | PascalCase | `QuartzComponent`, `FullSlug` |
|
|
| Type Guards | `is*` prefix | `isFilePath()`, `isFullSlug()` |
|
|
| Constants | UPPER_CASE | `QUARTZ`, `UPSTREAM_NAME` |
|
|
| Options types | `Options` | `interface Options { ... }` |
|
|
|
|
### Branded Types Pattern
|
|
|
|
This codebase uses branded types for type-safe path handling:
|
|
|
|
```typescript
|
|
type SlugLike<T> = string & { __brand: T }
|
|
export type FilePath = SlugLike<"filepath">
|
|
export type FullSlug = SlugLike<"full">
|
|
export type SimpleSlug = SlugLike<"simple">
|
|
|
|
// Always validate with type guards before using
|
|
export function isFilePath(s: string): s is FilePath { ... }
|
|
```
|
|
|
|
### Component Pattern (Preact)
|
|
|
|
Components use a factory function pattern with attached static properties:
|
|
|
|
```typescript
|
|
export default ((userOpts?: Partial<Options>) => {
|
|
const opts: Options = { ...defaultOptions, ...userOpts }
|
|
|
|
const ComponentName: QuartzComponent = ({ cfg, displayClass }: QuartzComponentProps) => {
|
|
return <div class={classNames(displayClass, "component-name")}>...</div>
|
|
}
|
|
|
|
ComponentName.css = style // SCSS styles
|
|
ComponentName.afterDOMLoaded = script // Client-side JS
|
|
return ComponentName
|
|
}) satisfies QuartzComponentConstructor
|
|
```
|
|
|
|
### Plugin Pattern
|
|
|
|
Three plugin types: transformers, filters, and emitters.
|
|
|
|
```typescript
|
|
export const PluginName: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
|
|
const opts = { ...defaultOptions, ...userOpts }
|
|
return {
|
|
name: "PluginName",
|
|
markdownPlugins(ctx) { return [...] },
|
|
htmlPlugins(ctx) { return [...] },
|
|
externalResources(ctx) { return { js: [], css: [] } },
|
|
}
|
|
}
|
|
```
|
|
|
|
### Testing Pattern
|
|
|
|
Use Node.js native test runner with `assert`:
|
|
|
|
```typescript
|
|
import test, { describe, beforeEach } from "node:test"
|
|
import assert from "node:assert"
|
|
|
|
describe("FeatureName", () => {
|
|
test("should do something", () => {
|
|
assert.strictEqual(actual, expected)
|
|
assert.deepStrictEqual(actualObj, expectedObj)
|
|
assert(condition) // truthy assertion
|
|
assert(!condition) // falsy assertion
|
|
})
|
|
})
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
- Use `try/catch` for critical operations (file I/O, parsing)
|
|
- Custom `trace` utility for error reporting with stack traces
|
|
- `process.exit(1)` for fatal errors
|
|
- `console.warn()` for non-fatal issues
|
|
|
|
### Async Patterns
|
|
|
|
- Prefer `async/await` over raw promises
|
|
- Use async generators (`async *emit()`) for streaming file output
|
|
- Use `async-mutex` for concurrent build protection
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
quartz/
|
|
├── bootstrap-cli.mjs # CLI entry point
|
|
├── build.ts # Build orchestration
|
|
├── cfg.ts # Configuration types
|
|
├── components/ # Preact UI components
|
|
│ ├── *.tsx # Components
|
|
│ ├── scripts/ # Client-side scripts (*.inline.ts)
|
|
│ └── styles/ # Component SCSS
|
|
├── plugins/
|
|
│ ├── transformers/ # Markdown AST transformers
|
|
│ ├── filters/ # Content filters
|
|
│ ├── emitters/ # Output generators
|
|
│ └── types.ts # Plugin type definitions
|
|
├── processors/ # Build pipeline (parse/filter/emit)
|
|
├── util/ # Utility functions
|
|
└── i18n/ # Internationalization (30+ locales)
|
|
```
|
|
|
|
## Branch Workflow
|
|
|
|
This is a fork of [jackyzha0/quartz](https://github.com/jackyzha0/quartz) with org-roam customizations.
|
|
|
|
| Branch | Purpose |
|
|
| ----------- | ------------------------------------------------ |
|
|
| `main` | Clean mirror of upstream quartz — no custom code |
|
|
| `org-roam` | Default branch — all customizations live here |
|
|
| `feature/*` | Short-lived branches off `org-roam` |
|
|
|
|
### Pulling Upstream Updates
|
|
|
|
```bash
|
|
git checkout main
|
|
git fetch upstream
|
|
git merge upstream/main
|
|
git checkout org-roam
|
|
git merge main
|
|
# Resolve conflicts if any, then commit
|
|
```
|
|
|
|
### Working on Features
|
|
|
|
```bash
|
|
git checkout org-roam
|
|
git checkout -b feature/my-feature
|
|
# ... work ...
|
|
git checkout org-roam
|
|
git merge feature/my-feature
|
|
git branch -d feature/my-feature
|
|
```
|
|
|
|
**Merge direction:** `upstream → main → org-roam → feature/*`
|
|
|
|
## Important Notes
|
|
|
|
- **Client-side scripts**: Use `.inline.ts` suffix, bundled via esbuild
|
|
- **Isomorphic code**: `quartz/util/path.ts` must not use Node.js APIs
|
|
- **Incremental builds**: Plugins can implement `partialEmit` for efficiency
|
|
- **Markdown flavors**: Supports Obsidian (`ofm.ts`) and Roam (`roam.ts`) syntax
|