Initial implementation of @quartz-community/types package

This commit is contained in:
saberzero1 2026-02-07 20:39:05 +01:00
parent 11813ef85c
commit a342579c84
No known key found for this signature in database
18 changed files with 311 additions and 4505 deletions

View file

@ -1,26 +0,0 @@
{
"root": true,
"env": {
"es2022": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": "latest",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"ignorePatterns": ["dist", "node_modules"],
"rules": {
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
]
}
}

View file

@ -1,55 +0,0 @@
name: CI
on:
push:
branches: ["main"]
tags: ["v*"]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run checks
run: npm run check
- name: Build
run: npm run build
publish:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
needs: [test]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -1,7 +0,0 @@
{
"semi": true,
"singleQuote": false,
"printWidth": 100,
"trailingComma": "all",
"arrowParens": "always"
}

View file

@ -1,12 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Initial Quartz community plugin template.

175
README.md
View file

@ -1,165 +1,42 @@
# Quartz Community Plugin Template
# @quartz-community/types
Production-ready template for building, testing, and publishing Quartz community plugins. It mirrors
Quartz's native plugin patterns and uses a factory-function API similar to Astro integrations:
plugins are created by functions that return objects with `name` and lifecycle hooks.
Type definitions for Quartz community plugins. This package provides TypeScript interfaces and types that mirror the internal Quartz types, enabling external plugins to have full type safety without depending on the core Quartz package.
## Highlights
- ✅ Quartz-compatible transformer/filter/emitter examples
- ✅ TypeScript-first with exported types for consumers
- ✅ `tsup` bundling + declaration output
- ✅ Vitest testing setup with example tests
- ✅ Linting/formatting with ESLint + Prettier
- ✅ CI workflow for checks and npm publishing
- ✅ Demonstrates CSS/JS resource injection and remark/rehype usage
## Getting started
## Installation
```bash
npm install
npm run build
npm install @quartz-community/types
```
## Usage in Quartz
## Usage
Install your plugin into a Quartz site and register it in `quartz.config.ts`:
```ts
```typescript
import {
ExampleTransformer,
ExampleFilter,
ExampleEmitter,
} from "@quartz-community/plugin-template";
QuartzComponent,
QuartzComponentProps,
ExplorerOptions,
} from "@quartz-community/types";
export default {
configuration: {
pageTitle: "My Garden",
},
plugins: {
transformers: [ExampleTransformer({ highlightToken: "==" })],
filters: [ExampleFilter({ allowDrafts: false })],
emitters: [ExampleEmitter({ manifestSlug: "plugin-manifest" })],
},
const MyComponent: QuartzComponent = (props: QuartzComponentProps) => {
// Component implementation
};
MyComponent.css = `
.my-component { /* styles */ }
`;
MyComponent.afterDOMLoaded = `
// Client-side script
`;
```
## Plugin factory pattern (Astro-style)
## Included Types
Quartz plugins are factory functions that return an object with a `name` and hook implementations.
This mirrors Astro's integration pattern (a function returning an object of hooks), which makes
composition and configuration explicit and predictable.
```ts
import type { QuartzTransformerPlugin } from "@jackyzha0/quartz/plugins/types";
export const MyTransformer: QuartzTransformerPlugin<{ enabled: boolean }> = (opts) => {
return {
name: "MyTransformer",
markdownPlugins() {
return [];
},
};
};
```
## Examples included
### Transformer
`ExampleTransformer` shows how to:
- apply a custom remark plugin
- run a rehype plugin
- inject CSS/JS resources
- perform a text transform hook
```ts
import { ExampleTransformer } from "@quartz-community/plugin-template";
ExampleTransformer({
highlightToken: "==",
headingClass: "example-plugin-heading",
enableGfm: true,
addHeadingSlugs: true,
});
```
The transformer uses a custom remark plugin to convert `==highlight==` into bold text and a rehype
plugin to attach a class to all headings. It also injects a small inline CSS/JS snippet.
### Filter
`ExampleFilter` demonstrates frontmatter-driven filtering:
```ts
ExampleFilter({
allowDrafts: false,
excludeTags: ["private", "wip"],
excludePathPrefixes: ["_drafts/", "_private/"],
});
```
### Emitter
`ExampleEmitter` emits a JSON manifest of all pages:
```ts
ExampleEmitter({
manifestSlug: "plugin-manifest",
includeFrontmatter: true,
metadata: { project: "My Garden" },
transformManifest: (json) => json.replace("My Garden", "Quartz"),
});
```
## API reference
### `ExampleTransformer(options)`
| Option | Type | Default | Description |
| ----------------- | --------- | -------------------------- | ----------------------------- |
| `highlightToken` | `string` | `"=="` | Token used to highlight text. |
| `headingClass` | `string` | `"example-plugin-heading"` | Class added to headings. |
| `enableGfm` | `boolean` | `true` | Enables `remark-gfm`. |
| `addHeadingSlugs` | `boolean` | `true` | Enables `rehype-slug`. |
### `ExampleFilter(options)`
| Option | Type | Default | Description |
| --------------------- | ---------- | --------------------------- | ------------------------- |
| `allowDrafts` | `boolean` | `false` | Publish draft pages. |
| `excludeTags` | `string[]` | `["private"]` | Tags to exclude. |
| `excludePathPrefixes` | `string[]` | `["_drafts/", "_private/"]` | Path prefixes to exclude. |
### `ExampleEmitter(options)`
| Option | Type | Default | Description |
| --------------------- | -------------------------- | ----------------------------------------- | ----------------------------------------- |
| `manifestSlug` | `string` | `"plugin-manifest"` | Output filename (without extension). |
| `includeFrontmatter` | `boolean` | `true` | Include frontmatter in output. |
| `metadata` | `Record<string, unknown>` | `{ generator: "Quartz Plugin Template" }` | Extra metadata in manifest. |
| `transformManifest` | `(json: string) => string` | `undefined` | Custom transformer for emitted JSON. |
| `manifestScriptClass` | `string` | `undefined` | Optional CSS class if rendered into HTML. |
## Testing
```bash
npm test
```
## Build and lint
```bash
npm run build
npm run lint
npm run format
```
## Publishing
Tags matching `v*` trigger the GitHub Actions publish workflow. Ensure `NPM_TOKEN` is set in the
repository secrets.
- **Component Types**: `QuartzComponent`, `QuartzComponentProps`, `StringResource`
- **Plugin Types**: `QuartzTransformerPlugin`, `QuartzFilterPlugin`, `QuartzEmitterPlugin`
- **Data Types**: `FileTrieNode`, `ContentIndex`, `ContentIndexEntry`
- **Component Options**: `ExplorerOptions`, `GraphOptions`, `SearchOptions`, `D3Config`
- **Utility Types**: `HTMLAttributes`, `EventHandler`, `CleanupFunction`, `ClassValue`
## License

3831
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,43 +1,31 @@
{
"name": "@quartz-community/plugin-template",
"name": "@quartz-community/types",
"version": "0.1.0",
"description": "Template repository for Quartz community plugins.",
"description": "Type definitions for Quartz community plugins",
"type": "module",
"license": "MIT",
"author": "Quartz Community",
"homepage": "https://quartz.jzhao.xyz",
"repository": {
"type": "git",
"url": "https://github.com/quartz-community/plugin-template"
"url": "https://github.com/quartz-community/types"
},
"keywords": [
"quartz",
"quartz-plugin",
"plugin-template",
"remark",
"rehype",
"mdast",
"hast"
"types",
"typescript"
],
"files": [
"dist",
"README.md",
"LICENSE",
"CHANGELOG.md"
"LICENSE"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.js"
},
"./components": {
"types": "./dist/components/index.d.ts",
"import": "./dist/components/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",
@ -47,47 +35,11 @@
"build": "tsup",
"prepare": "npm run build",
"dev": "tsup --watch",
"lint": "eslint . --max-warnings=0",
"format": "prettier . --check",
"test": "vitest run",
"typecheck": "tsc --noEmit",
"check": "npm run typecheck && npm run lint && npm run format && npm run test"
},
"peerDependencies": {
"@jackyzha0/quartz": "^4.5.2",
"preact": "^10.0.0"
},
"peerDependenciesMeta": {
"@jackyzha0/quartz": {
"optional": true
},
"preact": {
"optional": false
}
},
"dependencies": {
"mdast-util-find-and-replace": "^3.0.1",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.3"
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/hast": "^3.0.4",
"@types/mdast": "^4.0.4",
"@types/node": "^24.10.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"preact": "^10.28.2",
"prettier": "^3.6.2",
"tsup": "^8.5.0",
"typescript": "^5.9.3",
"vitest": "^2.1.9"
"typescript": "^5.9.3"
},
"engines": {
"node": ">=22",

View file

@ -1,59 +0,0 @@
import type { VNode, JSX } from "preact";
export interface ExampleComponentOptions {
prefix?: string;
suffix?: string;
className?: string;
}
export type QuartzComponentProps = {
ctx: any; // eslint-disable-line @typescript-eslint/no-explicit-any
externalResources: any; // eslint-disable-line @typescript-eslint/no-explicit-any
fileData: any; // eslint-disable-line @typescript-eslint/no-explicit-any
cfg: any; // eslint-disable-line @typescript-eslint/no-explicit-any
children: any; // eslint-disable-line @typescript-eslint/no-explicit-any
tree: any; // eslint-disable-line @typescript-eslint/no-explicit-any
allFiles: any[]; // eslint-disable-line @typescript-eslint/no-explicit-any
displayClass?: "mobile-only" | "desktop-only";
} & JSX.IntrinsicAttributes & {
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};
export interface QuartzComponent {
(props: QuartzComponentProps): VNode;
css?: string;
beforeDOMLoaded?: string;
afterDOMLoaded?: string;
}
export const ExampleComponent = (opts?: ExampleComponentOptions) => {
const { prefix = "", suffix = "", className = "example-component" } = opts ?? {};
const Component: QuartzComponent = (props: QuartzComponentProps) => {
const title = props.fileData?.frontmatter?.title ?? "Untitled";
const fullText = `${prefix}${title}${suffix}`;
return <div class={className}>{fullText}</div>;
};
Component.css = `
.example-component {
padding: 8px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 4px;
font-weight: 600;
display: inline-block;
}
`;
Component.afterDOMLoaded = `
document.addEventListener('nav', () => {
console.log('ExampleComponent: page navigation occurred');
});
`;
return Component;
};
export default ExampleComponent;

View file

@ -1 +0,0 @@
export { ExampleComponent, type ExampleComponentOptions } from "./ExampleComponent";

View file

@ -1,11 +1,265 @@
export { ExampleTransformer } from "./transformer";
export { ExampleFilter } from "./filter";
export { ExampleEmitter } from "./emitter";
export { ExampleComponent } from "./components/ExampleComponent";
/**
* Core type definitions for Quartz community plugins
* These types mirror the internal Quartz types but are available as a standalone package
* to avoid circular dependencies and version conflicts.
*/
export type {
ExampleTransformerOptions,
ExampleFilterOptions,
ExampleEmitterOptions,
ExampleComponentOptions,
} from "./types";
// ============================================================================
// Component Types
// ============================================================================
/**
* Props passed to Quartz components during rendering
*/
export interface QuartzComponentProps {
/** Quartz context object */
ctx: any;
/** Data about the current file being rendered */
fileData: any;
/** Global Quartz configuration */
cfg: any;
/** Layout configuration */
layout: any;
/** File tree for navigation components */
tree: any;
/** All files in the site */
allFiles: any;
}
/**
* Function type for Quartz components
*/
export type QuartzComponentFunction = (props: QuartzComponentProps) => any;
/**
* A Quartz component with optional CSS and scripts
*/
export interface QuartzComponent extends QuartzComponentFunction {
/** CSS styles to inject into the page */
css?: string;
/** Script to run after DOM is loaded */
afterDOMLoaded?: string;
/** Script to run before DOM is loaded */
beforeDOMLoaded?: string;
}
/**
* Resource that can be a single string or array of strings
*/
export type StringResource = string | string[];
// ============================================================================
// Plugin Types
// ============================================================================
/**
* Transformer plugin that modifies markdown/HTML content
*/
export interface QuartzTransformerPluginInstance {
name: string;
/** Markdown processing plugins */
markdownPlugins?: () => any[];
/** HTML processing plugins */
htmlPlugins?: () => any[];
/** Text transformation function */
textTransform?: (ctx: any, text: string) => string;
}
export type QuartzTransformerPlugin<Options = undefined> = (
opts: Options,
) => QuartzTransformerPluginInstance;
/**
* Filter plugin that determines which files to publish
*/
export interface QuartzFilterPluginInstance {
name: string;
/** Returns true if file should be published */
shouldPublish?: (ctx: any, file: any) => boolean;
}
export type QuartzFilterPlugin<Options = undefined> = (
opts: Options,
) => QuartzFilterPluginInstance;
/**
* Emitter plugin that generates output files
*/
export interface QuartzEmitterPluginInstance {
name: string;
/** Emit files to the output directory */
emit?: (ctx: any, content: any[]) => Promise<any[]>;
/** Get dependencies for incremental builds */
getDependencies?: (ctx: any, content: any[]) => Promise<string[]>;
}
export type QuartzEmitterPlugin<Options = undefined> = (
opts: Options,
) => QuartzEmitterPluginInstance;
// ============================================================================
// File/Data Types
// ============================================================================
/**
* Node in the file tree (used by Explorer component)
*/
export interface FileTrieNode {
/** URL slug segments */
slugSegments: string[];
/** File data object */
data: any | null;
/** Child nodes */
children: FileTrieNode[];
/** Whether this is a folder */
isFolder: boolean;
/** File segment hint for display */
fileSegmentHint?: string;
}
/**
* Configuration for file tree operations
*/
export interface FileTrieConfig {
/** Sort function for entries */
sortFn?: (a: FileTrieNode, b: FileTrieNode) => number;
/** Filter function for entries */
filterFn?: (node: FileTrieNode) => boolean;
/** Map function for transforming entries */
mapFn?: (node: FileTrieNode) => void;
/** Order of operations: filter, map, sort */
order?: Array<"filter" | "map" | "sort">;
}
/**
* Content index entry
*/
export interface ContentIndexEntry {
slug: string;
filePath: string;
title: string;
content: string;
tags: string[];
links: string[];
}
/**
* Full content index
*/
export type ContentIndex = Record<string, ContentIndexEntry>;
// ============================================================================
// Component Option Types
// ============================================================================
/**
* Common options for all component factory functions
*/
export interface ComponentOptions {
/** Component title/label */
title?: string;
}
/**
* Explorer component options
*/
export interface ExplorerOptions extends ComponentOptions {
/** Default state for folders: "collapsed" or "open" */
folderDefaultState: "collapsed" | "open";
/** Behavior when clicking folders: "collapse" or "link" */
folderClickBehavior: "collapse" | "link";
/** Whether to persist folder state in localStorage */
useSavedState: boolean;
/** Custom sort function */
sortFn?: (a: FileTrieNode, b: FileTrieNode) => number;
/** Custom filter function */
filterFn?: (node: FileTrieNode) => boolean;
/** Custom map function */
mapFn?: (node: FileTrieNode) => void;
/** Order of operations */
order?: Array<"filter" | "map" | "sort">;
}
/**
* D3 graph configuration
*/
export interface D3Config {
/** Enable node dragging */
drag: boolean;
/** Enable zooming */
zoom: boolean;
/** Depth of connections (-1 for all) */
depth: number;
/** Scale factor */
scale: number;
/** Repelling force strength */
repelForce: number;
/** Center force strength */
centerForce: number;
/** Distance between linked nodes */
linkDistance: number;
/** Label font size */
fontSize: number;
/** Opacity multiplier */
opacityScale: number;
/** Tags to exclude */
removeTags: string[];
/** Show tags as nodes */
showTags: boolean;
/** Dim non-hovered nodes */
focusOnHover?: boolean;
/** Enable radial layout */
enableRadial?: boolean;
}
/**
* Graph component options
*/
export interface GraphOptions {
/** Local page graph configuration */
localGraph?: Partial<D3Config>;
/** Global site graph configuration */
globalGraph?: Partial<D3Config>;
}
/**
* Search component options
*/
export interface SearchOptions extends ComponentOptions {
/** Enable content preview panel */
enablePreview?: boolean;
/** Custom placeholder text */
placeholder?: string;
}
// ============================================================================
// Utility Types
// ============================================================================
/**
* HTML attributes for components
*/
export interface HTMLAttributes {
[key: string]: string | number | boolean | undefined;
}
/**
* Event handler type
*/
export type EventHandler = (event: Event) => void;
/**
* Cleanup function returned by event listeners
*/
export type CleanupFunction = () => void;
/**
* Class name value (for classNames utility)
*/
export type ClassValue =
| string
| number
| boolean
| undefined
| null
| ClassValue[];

View file

@ -1,45 +0,0 @@
import { describe, expect, it } from "vitest";
import path from "node:path";
import fs from "node:fs/promises";
import { tmpdir } from "node:os";
import { ExampleEmitter } from "../src/emitter";
import { createCtx, createProcessedContent } from "./helpers";
describe("ExampleEmitter", () => {
it("writes a manifest to the output directory", async () => {
const outputDir = await fs.mkdtemp(path.join(tmpdir(), "quartz-plugin-"));
const ctx = createCtx({ argv: { output: outputDir } });
const emitter = ExampleEmitter({ manifestSlug: "manifest" });
const content = [
createProcessedContent({
slug: "hello-world",
filePath: "notes/hello-world.md",
frontmatter: { title: "Hello", tags: ["docs"] },
}),
];
const result = await emitter.emit(ctx, content, {
css: [],
js: [],
additionalHead: [],
});
const outputPaths = Array.isArray(result) ? result : await collectAsync(result);
const outputPath = outputPaths[0];
if (!outputPath) {
throw new Error("Expected emitter to return an output path");
}
const manifest = JSON.parse(await fs.readFile(outputPath, "utf8"));
expect(outputPath).toContain("manifest.json");
expect(manifest.pages[0].slug).toBe("hello-world");
});
});
const collectAsync = async <T>(iterable: AsyncIterable<T>): Promise<T[]> => {
const results: T[] = [];
for await (const item of iterable) {
results.push(item);
}
return results;
};

View file

@ -1,21 +0,0 @@
import { describe, expect, it } from "vitest";
import { ExampleFilter } from "../src/filter";
import { createCtx, createProcessedContent } from "./helpers";
describe("ExampleFilter", () => {
it("filters drafts by default", () => {
const ctx = createCtx();
const filter = ExampleFilter();
const content = createProcessedContent({ frontmatter: { draft: true } });
expect(filter.shouldPublish(ctx, content)).toBe(false);
});
it("allows drafts when configured", () => {
const ctx = createCtx();
const filter = ExampleFilter({ allowDrafts: true });
const content = createProcessedContent({ frontmatter: { draft: true } });
expect(filter.shouldPublish(ctx, content)).toBe(true);
});
});

View file

@ -1,38 +0,0 @@
import type { BuildCtx } from "@jackyzha0/quartz/util/ctx";
import type { QuartzConfig } from "@jackyzha0/quartz/cfg";
import type { ProcessedContent, QuartzPluginData } from "@jackyzha0/quartz/plugins/vfile";
import { VFile } from "vfile";
type BuildCtxOverrides = Omit<Partial<BuildCtx>, "argv"> & {
argv?: Partial<BuildCtx["argv"]>;
};
export const createCtx = (overrides: BuildCtxOverrides = {}): BuildCtx => {
const { argv: argvOverrides, ...rest } = overrides;
const argv: BuildCtx["argv"] = {
directory: "content",
verbose: false,
output: "dist",
serve: false,
watch: false,
port: 0,
wsPort: 0,
...argvOverrides,
};
return {
buildId: "test-build",
argv,
cfg: {} as QuartzConfig,
allSlugs: [],
allFiles: [],
incremental: false,
...rest,
};
};
export const createProcessedContent = (data: Partial<QuartzPluginData> = {}): ProcessedContent => {
const vfile = new VFile("");
vfile.data = data;
return [{ type: "root", children: [] }, vfile];
};

View file

@ -1,22 +0,0 @@
import { describe, expect, it } from "vitest";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import { ExampleTransformer } from "../src/transformer";
import { createCtx } from "./helpers";
describe("ExampleTransformer", () => {
it("highlights text wrapped in the token", async () => {
const ctx = createCtx();
const transformer = ExampleTransformer({ highlightToken: "==" });
const plugins = transformer.markdownPlugins?.(ctx) ?? [];
const file = await unified()
.use(remarkParse)
.use(plugins)
.use(remarkStringify)
.process("Hello ==Quartz==");
expect(String(file)).toContain("**Quartz**");
});
});

View file

@ -1,26 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"module": "NodeNext",
"lib": ["ES2022", "DOM"],
"rootDir": ".",
"outDir": "dist",
"moduleResolution": "NodeNext",
"rootDir": "./src",
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noEmit": true,
"strict": true,
"noImplicitOverride": true,
"noUncheckedIndexedAccess": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": ["vitest/globals", "node"],
"verbatimModuleSyntax": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
"esModuleInterop": true
},
"include": ["src", "test", "types", "tsup.config.ts", "vitest.config.ts"],
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}

View file

@ -3,19 +3,11 @@ import { defineConfig } from "tsup";
export default defineConfig({
entry: {
index: "src/index.ts",
types: "src/types.ts",
"components/index": "src/components/index.ts",
},
format: ["esm"],
dts: true,
sourcemap: true,
clean: true,
treeshake: true,
target: "es2022",
splitting: false,
outDir: "dist",
esbuildOptions(options) {
options.jsx = "automatic";
options.jsxImportSource = "preact";
},
});

View file

@ -1,150 +0,0 @@
declare module "@jackyzha0/quartz/components/types" {
export type QuartzComponent = unknown;
}
declare module "@jackyzha0/quartz/util/path" {
export type FilePath = string & { _brand: "FilePath" };
export type FullSlug = string & { _brand: "FullSlug" };
export function joinSegments(...segments: string[]): FilePath;
}
declare module "@jackyzha0/quartz/util/resources" {
export type JSResource =
| {
loadTime: "beforeDOMReady" | "afterDOMReady";
moduleType?: "module";
spaPreserve?: boolean;
src: string;
contentType: "external";
}
| {
loadTime: "beforeDOMReady" | "afterDOMReady";
moduleType?: "module";
spaPreserve?: boolean;
script: string;
contentType: "inline";
};
export type CSSResource = {
content: string;
inline?: boolean;
spaPreserve?: boolean;
};
export interface StaticResources {
css: CSSResource[];
js: JSResource[];
additionalHead: unknown[];
}
}
declare module "@jackyzha0/quartz/util/ctx" {
import type { QuartzConfig } from "@jackyzha0/quartz/cfg";
import type { FilePath, FullSlug } from "@jackyzha0/quartz/util/path";
export interface Argv {
directory: string;
verbose: boolean;
output: string;
serve: boolean;
watch: boolean;
port: number;
wsPort: number;
remoteDevHost?: string;
concurrency?: number;
}
export interface BuildCtx {
buildId: string;
argv: Argv;
cfg: QuartzConfig;
allSlugs: FullSlug[];
allFiles: FilePath[];
incremental: boolean;
}
}
declare module "@jackyzha0/quartz/cfg" {
export interface QuartzConfig {
configuration: {
baseUrl?: string;
};
}
}
declare module "@jackyzha0/quartz/plugins/vfile" {
import type { Root as HtmlRoot } from "hast";
import type { Root as MdRoot } from "mdast";
import type { Data, VFile } from "vfile";
export type QuartzPluginData = Data;
export type MarkdownContent = [MdRoot, VFile];
export type ProcessedContent = [HtmlRoot, VFile];
}
declare module "@jackyzha0/quartz/plugins/types" {
import type { PluggableList } from "unified";
import type { StaticResources } from "@jackyzha0/quartz/util/resources";
import type { ProcessedContent } from "@jackyzha0/quartz/plugins/vfile";
import type { QuartzComponent } from "@jackyzha0/quartz/components/types";
import type { FilePath } from "@jackyzha0/quartz/util/path";
import type { BuildCtx } from "@jackyzha0/quartz/util/ctx";
import type { VFile } from "vfile";
export interface PluginTypes {
transformers: QuartzTransformerPluginInstance[];
filters: QuartzFilterPluginInstance[];
emitters: QuartzEmitterPluginInstance[];
}
type OptionType = object | undefined;
type ExternalResourcesFn = (ctx: BuildCtx) => Partial<StaticResources> | undefined;
export type QuartzTransformerPlugin<Options extends OptionType = undefined> = (
opts?: Options,
) => QuartzTransformerPluginInstance;
export type QuartzTransformerPluginInstance = {
name: string;
textTransform?: (ctx: BuildCtx, src: string) => string;
markdownPlugins?: (ctx: BuildCtx) => PluggableList;
htmlPlugins?: (ctx: BuildCtx) => PluggableList;
externalResources?: ExternalResourcesFn;
};
export type QuartzFilterPlugin<Options extends OptionType = undefined> = (
opts?: Options,
) => QuartzFilterPluginInstance;
export type QuartzFilterPluginInstance = {
name: string;
shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean;
};
export type ChangeEvent = {
type: "add" | "change" | "delete";
path: FilePath;
file?: VFile;
};
export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
opts?: Options,
) => QuartzEmitterPluginInstance;
export type QuartzEmitterPluginInstance = {
name: string;
emit: (
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
) => Promise<FilePath[]> | AsyncGenerator<FilePath>;
partialEmit?: (
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
changeEvents: ChangeEvent[],
) => Promise<FilePath[]> | AsyncGenerator<FilePath> | null;
getQuartzComponents?: (ctx: BuildCtx) => QuartzComponent[];
externalResources?: ExternalResourcesFn;
};
}

View file

@ -1,9 +0,0 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
include: ["test/**/*.test.ts"],
reporters: ["default"],
},
});