@aerobuilt/content 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import { ContentCollectionConfig, ContentConfig, ContentDocument, ContentMeta, MarkdownConfig, defineCollection, defineConfig } from "./types.mjs";
2
+ import { render } from "./render.mjs";
3
+ export { type ContentCollectionConfig, type ContentConfig, type ContentDocument, type ContentMeta, type MarkdownConfig, defineCollection, defineConfig, render };
package/dist/index.mjs ADDED
@@ -0,0 +1,4 @@
1
+ import { defineCollection, defineConfig } from "./types.mjs";
2
+ import { render } from "./render.mjs";
3
+
4
+ export { defineCollection, defineConfig, render };
@@ -0,0 +1,12 @@
1
+ import { ContentDocument } from "./types.mjs";
2
+
3
+ //#region src/markdown.d.ts
4
+ /**
5
+ * Compile a document's markdown body to HTML. Use in `transform` to attach `html` to each document.
6
+ *
7
+ * @param document - Content document (body is compiled).
8
+ * @returns HTML string.
9
+ */
10
+ declare function compileMarkdown(document: ContentDocument): Promise<string>;
11
+ //#endregion
12
+ export { compileMarkdown };
@@ -0,0 +1,16 @@
1
+ import { getProcessor } from "./processor.mjs";
2
+
3
+ //#region src/markdown.ts
4
+ /**
5
+ * Compile a document's markdown body to HTML. Use in `transform` to attach `html` to each document.
6
+ *
7
+ * @param document - Content document (body is compiled).
8
+ * @returns HTML string.
9
+ */
10
+ async function compileMarkdown(document) {
11
+ const result = await getProcessor().process(document.body);
12
+ return String(result);
13
+ }
14
+
15
+ //#endregion
16
+ export { compileMarkdown };
@@ -1,20 +1,7 @@
1
- import { Processor } from 'unified';
2
- import { M as MarkdownConfig } from './types-D4g4HdAa.js';
3
- import 'zod';
4
-
5
- /**
6
- * Shared markdown processor: unified rehype pipeline with pluggable remark and rehype plugins.
7
- *
8
- * @remarks
9
- * Both `compileMarkdown` (eager, for transforms) and `render` (lazy, for pages) delegate to
10
- * this module. Call `initProcessor(config)` once at startup (from the Vite plugin) to
11
- * configure the pipeline. The pipeline is always rehype-based:
12
- *
13
- * `remark` -> `[remarkPlugins]` -> `remark-rehype` -> `[rehypePlugins]` -> `rehype-stringify`
14
- *
15
- * The processor is created lazily on first use if `initProcessor` was never called.
16
- */
1
+ import { MarkdownConfig } from "./types.mjs";
2
+ import { Processor } from "unified";
17
3
 
4
+ //#region src/processor.d.ts
18
5
  /**
19
6
  * Processor config: remark and rehype plugin arrays.
20
7
  *
@@ -24,10 +11,10 @@ import 'zod';
24
11
  */
25
12
  type ProcessorConfig = MarkdownConfig;
26
13
  declare global {
27
- var __aeroProcessorState: {
28
- processor: Processor | null;
29
- initialized: boolean;
30
- };
14
+ var __aeroProcessorState: {
15
+ processor: Processor | null;
16
+ initialized: boolean;
17
+ };
31
18
  }
32
19
  /**
33
20
  * Initialize the shared markdown processor.
@@ -56,5 +43,5 @@ declare function getProcessor(): Processor;
56
43
  * Reset the processor state. Intended for testing only.
57
44
  */
58
45
  declare function resetProcessor(): void;
59
-
60
- export { type ProcessorConfig, getProcessor, initProcessor, resetProcessor };
46
+ //#endregion
47
+ export { ProcessorConfig, getProcessor, initProcessor, resetProcessor };
@@ -0,0 +1,72 @@
1
+ import { remark } from "remark";
2
+ import remarkRehype from "remark-rehype";
3
+ import rehypeStringify from "rehype-stringify";
4
+
5
+ //#region src/processor.ts
6
+ if (!globalThis.__aeroProcessorState) globalThis.__aeroProcessorState = {
7
+ processor: null,
8
+ initialized: false
9
+ };
10
+ /**
11
+ * Create the unified processor pipeline.
12
+ *
13
+ * @remarks
14
+ * Always uses the rehype path: remark -> remarkPlugins -> remark-rehype -> rehypePlugins -> rehype-stringify.
15
+ * Without any rehype plugins, code blocks render as plain `<pre><code>`.
16
+ */
17
+ function applyPlugins(pipeline, plugins) {
18
+ for (const entry of plugins) if (Array.isArray(entry)) {
19
+ const [plugin, ...options] = entry;
20
+ pipeline = pipeline.use(plugin, ...options);
21
+ } else pipeline = pipeline.use(entry);
22
+ return pipeline;
23
+ }
24
+ function createProcessor(remarkPlugins = [], rehypePlugins = []) {
25
+ let pipeline = remark();
26
+ pipeline = applyPlugins(pipeline, remarkPlugins);
27
+ pipeline = pipeline.use(remarkRehype);
28
+ pipeline = applyPlugins(pipeline, rehypePlugins);
29
+ return pipeline.use(rehypeStringify);
30
+ }
31
+ /**
32
+ * Initialize the shared markdown processor.
33
+ *
34
+ * @remarks
35
+ * Call once at startup (typically from the Vite plugin's configResolved hook) before any
36
+ * markdown compilation occurs. Pass remark/rehype plugins to extend the pipeline (e.g.
37
+ * add `@shikijs/rehype` or `rehype-pretty-code` for syntax highlighting).
38
+ *
39
+ * Safe to call multiple times — subsequent calls replace the processor.
40
+ *
41
+ * @param config - Optional markdown plugin configuration.
42
+ */
43
+ async function initProcessor(config) {
44
+ globalThis.__aeroProcessorState.processor = createProcessor(config?.remarkPlugins ?? [], config?.rehypePlugins ?? []);
45
+ globalThis.__aeroProcessorState.initialized = true;
46
+ }
47
+ /**
48
+ * Get the shared markdown processor.
49
+ *
50
+ * @remarks
51
+ * Returns the processor created by `initProcessor`. If `initProcessor` was never called,
52
+ * lazily creates a default processor (no plugins).
53
+ *
54
+ * @returns The unified processor instance.
55
+ */
56
+ function getProcessor() {
57
+ if (!globalThis.__aeroProcessorState.processor) {
58
+ globalThis.__aeroProcessorState.processor = createProcessor();
59
+ globalThis.__aeroProcessorState.initialized = true;
60
+ }
61
+ return globalThis.__aeroProcessorState.processor;
62
+ }
63
+ /**
64
+ * Reset the processor state. Intended for testing only.
65
+ */
66
+ function resetProcessor() {
67
+ globalThis.__aeroProcessorState.processor = null;
68
+ globalThis.__aeroProcessorState.initialized = false;
69
+ }
70
+
71
+ //#endregion
72
+ export { getProcessor, initProcessor, resetProcessor };
@@ -0,0 +1,14 @@
1
+ import { ContentDocument } from "./types.mjs";
2
+
3
+ //#region src/render.d.ts
4
+ /**
5
+ * Render a content document's markdown body to HTML. Use in pages with documents from getCollection.
6
+ *
7
+ * @param doc - Content document (or null/undefined; returns empty HTML and logs a warning).
8
+ * @returns `{ html: string }`.
9
+ */
10
+ declare function render(doc: ContentDocument | null | undefined): Promise<{
11
+ html: string;
12
+ }>;
13
+ //#endregion
14
+ export { render };
@@ -0,0 +1,20 @@
1
+ import { getProcessor } from "./processor.mjs";
2
+
3
+ //#region src/render.ts
4
+ /**
5
+ * Render a content document's markdown body to HTML. Use in pages with documents from getCollection.
6
+ *
7
+ * @param doc - Content document (or null/undefined; returns empty HTML and logs a warning).
8
+ * @returns `{ html: string }`.
9
+ */
10
+ async function render(doc) {
11
+ if (!doc) {
12
+ console.warn("[aero] render() received null or undefined document. Returning empty HTML.");
13
+ return { html: "" };
14
+ }
15
+ const result = await getProcessor().process(doc.body);
16
+ return { html: String(result) };
17
+ }
18
+
19
+ //#endregion
20
+ export { render };
@@ -0,0 +1,82 @@
1
+ import { Pluggable } from "unified";
2
+ import { ZodType } from "zod";
3
+
4
+ //#region src/types.d.ts
5
+ /** Metadata attached to every content document (path, slug, filename, extension). */
6
+ interface ContentMeta {
7
+ /** Path relative to the collection directory (no extension). */
8
+ path: string;
9
+ /** Slug derived from the filename (no extension). */
10
+ slug: string;
11
+ /** The raw filename including extension. */
12
+ filename: string;
13
+ /** The file extension (e.g. `'.md'`). */
14
+ extension: string;
15
+ }
16
+ /** A content document: id, validated frontmatter (`data`), raw body, and `_meta`. */
17
+ interface ContentDocument<TSchema extends Record<string, any> = Record<string, any>> {
18
+ /** Unique identifier: collection-relative path without extension. */
19
+ id: string;
20
+ /** Validated frontmatter fields. */
21
+ data: TSchema;
22
+ /** Raw body content (everything after frontmatter). */
23
+ body: string;
24
+ /** Auto-generated metadata. */
25
+ _meta: ContentMeta;
26
+ }
27
+ /** Single collection definition: name, directory, glob, optional schema and transform. */
28
+ interface ContentCollectionConfig<TSchema extends Record<string, any> = Record<string, any>, TOutput = ContentDocument<TSchema>> {
29
+ /** Unique collection name (used as the export key, e.g. `allDocs`). */
30
+ name: string;
31
+ /** Directory to scan, relative to project root. */
32
+ directory: string;
33
+ /** Glob pattern for files to include (default: `**\/*.md`). */
34
+ include?: string;
35
+ /** Zod schema for frontmatter validation; `body` is always present. */
36
+ schema?: ZodType<TSchema>;
37
+ /** Optional async transform after validation; receives document, returns final shape. */
38
+ transform?: (document: ContentDocument<TSchema>) => TOutput | Promise<TOutput>;
39
+ }
40
+ /**
41
+ * Markdown pipeline configuration: custom remark and rehype plugins.
42
+ *
43
+ * @remarks
44
+ * The pipeline is: `remark` -> `[remarkPlugins]` -> `remark-rehype` -> `[rehypePlugins]` -> `rehype-stringify`.
45
+ * Add syntax highlighting (e.g. `@shikijs/rehype`, `rehype-pretty-code`) as a rehype plugin.
46
+ */
47
+ interface MarkdownConfig {
48
+ /** Remark plugins to apply before the remark-to-rehype bridge. */
49
+ remarkPlugins?: Pluggable[];
50
+ /** Rehype plugins to apply after remark-rehype and before rehype-stringify. */
51
+ rehypePlugins?: Pluggable[];
52
+ }
53
+ /** Top-level content config: array of collection definitions with optional markdown pipeline plugins. */
54
+ interface ContentConfig {
55
+ collections: ContentCollectionConfig<any, any>[];
56
+ /**
57
+ * Custom remark and rehype plugins for the markdown pipeline.
58
+ *
59
+ * @remarks
60
+ * The pipeline is always rehype-based:
61
+ * `remark` -> `[remarkPlugins]` -> `remark-rehype` -> `[rehypePlugins]` -> `rehype-stringify`.
62
+ * Without any rehype plugins, code blocks render as plain `<pre><code>`.
63
+ * Add `@shikijs/rehype` or `rehype-pretty-code` as a rehype plugin for syntax highlighting.
64
+ */
65
+ markdown?: MarkdownConfig;
66
+ }
67
+ /**
68
+ * Define a content collection (typed helper for content.config.ts).
69
+ *
70
+ * @param config - Collection config (name, directory, include, schema, transform).
71
+ * @returns The same config (unchanged).
72
+ */
73
+ declare function defineCollection<TSchema extends Record<string, any> = Record<string, any>, TOutput = ContentDocument<TSchema>>(config: ContentCollectionConfig<TSchema, TOutput>): ContentCollectionConfig<TSchema, TOutput>;
74
+ /**
75
+ * Define the content config (typed helper for content.config.ts).
76
+ *
77
+ * @param config - Config with `collections` array and optional `markdown` pipeline plugins.
78
+ * @returns The same config (unchanged).
79
+ */
80
+ declare function defineConfig(config: ContentConfig): ContentConfig;
81
+ //#endregion
82
+ export { ContentCollectionConfig, ContentConfig, ContentDocument, ContentMeta, MarkdownConfig, defineCollection, defineConfig };
package/dist/types.mjs ADDED
@@ -0,0 +1,22 @@
1
+ //#region src/types.ts
2
+ /**
3
+ * Define a content collection (typed helper for content.config.ts).
4
+ *
5
+ * @param config - Collection config (name, directory, include, schema, transform).
6
+ * @returns The same config (unchanged).
7
+ */
8
+ function defineCollection(config) {
9
+ return config;
10
+ }
11
+ /**
12
+ * Define the content config (typed helper for content.config.ts).
13
+ *
14
+ * @param config - Config with `collections` array and optional `markdown` pipeline plugins.
15
+ * @returns The same config (unchanged).
16
+ */
17
+ function defineConfig(config) {
18
+ return config;
19
+ }
20
+
21
+ //#endregion
22
+ export { defineCollection, defineConfig };
@@ -1,9 +1,10 @@
1
- import { Plugin } from 'vite';
1
+ import { Plugin } from "vite";
2
2
 
3
+ //#region src/vite.d.ts
3
4
  /** Options for the content plugin (config file path). */
4
5
  interface AeroContentOptions {
5
- /** Path to content config file relative to project root (default: `content.config.ts`). */
6
- config?: string;
6
+ /** Path to content config file relative to project root (default: `content.config.ts`). */
7
+ config?: string;
7
8
  }
8
9
  /**
9
10
  * Vite plugin that provides the `aero:content` virtual module (getCollection, render, serialized data).
@@ -12,5 +13,5 @@ interface AeroContentOptions {
12
13
  * @returns Vite plugin.
13
14
  */
14
15
  declare function aeroContent(options?: AeroContentOptions): Plugin;
15
-
16
- export { type AeroContentOptions, aeroContent };
16
+ //#endregion
17
+ export { AeroContentOptions, aeroContent };
package/dist/vite.mjs ADDED
@@ -0,0 +1,165 @@
1
+ import { initProcessor } from "./processor.mjs";
2
+ import fg from "fast-glob";
3
+ import matter from "gray-matter";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+
8
+ //#region src/loader.ts
9
+ /** Load one collection: glob files in directory, parse frontmatter, validate schema, apply transform. */
10
+ async function loadCollection(config, root) {
11
+ const dir = path.resolve(root, config.directory);
12
+ const files = await fg(config.include || "**/*.md", {
13
+ cwd: dir,
14
+ absolute: true
15
+ });
16
+ const documents = [];
17
+ for (const file of files) {
18
+ const { data: frontmatter, content: body } = matter(fs.readFileSync(file, "utf-8"));
19
+ const relPath = path.relative(dir, file);
20
+ const parsed = path.parse(relPath);
21
+ const id = parsed.dir ? `${parsed.dir}/${parsed.name}` : parsed.name;
22
+ const meta = {
23
+ path: id,
24
+ slug: parsed.name,
25
+ filename: parsed.base,
26
+ extension: parsed.ext
27
+ };
28
+ let validated = frontmatter;
29
+ if (config.schema) {
30
+ const result = config.schema.safeParse(frontmatter);
31
+ if (!result.success) {
32
+ const errors = "error" in result ? result.error?.issues?.map((i) => i.message).join(", ") : "Validation failed";
33
+ console.warn(`[aero:content] ⚠ Skipping "${relPath}" in collection "${config.name}": ${errors}`);
34
+ continue;
35
+ }
36
+ validated = result.data;
37
+ }
38
+ const doc = {
39
+ id,
40
+ data: validated,
41
+ body,
42
+ _meta: meta
43
+ };
44
+ if (config.transform) documents.push(await config.transform(doc));
45
+ else documents.push(doc);
46
+ }
47
+ return documents;
48
+ }
49
+ /**
50
+ * Load all collections; returns a map from collection name to document array.
51
+ *
52
+ * @param config - ContentConfig (collections array).
53
+ * @param root - Project root.
54
+ * @returns Map<collectionName, documents[]>.
55
+ */
56
+ async function loadAllCollections(config, root) {
57
+ const result = /* @__PURE__ */ new Map();
58
+ for (const collection of config.collections) {
59
+ const docs = await loadCollection(collection, root);
60
+ result.set(collection.name, docs);
61
+ }
62
+ return result;
63
+ }
64
+ /** Absolute paths of all collection directories (for HMR watch and invalidation). */
65
+ function getWatchedDirs(config, root) {
66
+ return config.collections.map((c) => path.resolve(root, c.directory));
67
+ }
68
+ /**
69
+ * Serialize loaded collections into ESM source: `__collections` object, `getCollection(name, filterFn)`, and re-export of `render`.
70
+ *
71
+ * @param loaded - Map of collection name → document array (from loadAllCollections).
72
+ * @returns Full module source string for the virtual module.
73
+ */
74
+ function serializeContentModule(loaded) {
75
+ return `
76
+ const __collections = {
77
+ ${Array.from(loaded.entries()).map(([name, docs]) => ` ${JSON.stringify(name)}: ${JSON.stringify(docs, null, 2)}`).join(",\n")}
78
+ };
79
+
80
+ export function getCollection(name, filterFn) {
81
+ let data = __collections[name];
82
+ if (!data) throw new Error(\`[aero:content] Collection "\${name}" not found. Available: \${Object.keys(__collections).join(", ")}\`);
83
+
84
+ if (import.meta.env.PROD) {
85
+ data = data.filter(item => item.data.published === true);
86
+ }
87
+
88
+ if (typeof filterFn === "function") {
89
+ return data.filter(filterFn);
90
+ }
91
+
92
+ return data;
93
+ }
94
+
95
+ export { render } from '@aerobuilt/content/render';
96
+ `;
97
+ }
98
+
99
+ //#endregion
100
+ //#region src/vite.ts
101
+ const CONTENT_MODULE_ID = "aero:content";
102
+ const RESOLVED_CONTENT_MODULE_ID = "\0aero:content";
103
+ const CONFIG_FILE = "content.config.ts";
104
+ /**
105
+ * Vite plugin that provides the `aero:content` virtual module (getCollection, render, serialized data).
106
+ *
107
+ * @param options - Optional config file path.
108
+ * @returns Vite plugin.
109
+ */
110
+ function aeroContent(options = {}) {
111
+ let resolvedConfig;
112
+ let contentConfig = null;
113
+ let serialized = null;
114
+ let watchedDirs = [];
115
+ return {
116
+ name: "vite-plugin-aero-content",
117
+ async configResolved(config) {
118
+ resolvedConfig = config;
119
+ const root = config.root;
120
+ const configPath = path.resolve(root, options.config || CONFIG_FILE);
121
+ try {
122
+ contentConfig = (await import(pathToFileURL(configPath).href)).default;
123
+ watchedDirs = getWatchedDirs(contentConfig, root);
124
+ await initProcessor(contentConfig.markdown);
125
+ } catch (err) {
126
+ if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "ENOENT") {
127
+ config.logger.warn(`[aero:content] No config found at "${configPath}". Content collections disabled.`);
128
+ return;
129
+ }
130
+ throw err;
131
+ }
132
+ },
133
+ resolveId(id) {
134
+ if (id === CONTENT_MODULE_ID) return RESOLVED_CONTENT_MODULE_ID;
135
+ if (id.startsWith(CONTENT_MODULE_ID + "/")) return RESOLVED_CONTENT_MODULE_ID;
136
+ return null;
137
+ },
138
+ async load(id) {
139
+ if (id !== RESOLVED_CONTENT_MODULE_ID) return null;
140
+ if (!contentConfig) return "// aero:content — no collections configured\n";
141
+ serialized = serializeContentModule(await loadAllCollections(contentConfig, resolvedConfig.root));
142
+ return serialized;
143
+ },
144
+ handleHotUpdate({ file, server }) {
145
+ if (!watchedDirs.some((dir) => file.startsWith(dir))) return;
146
+ const mod = server.moduleGraph.getModuleById(RESOLVED_CONTENT_MODULE_ID);
147
+ if (mod) {
148
+ server.moduleGraph.invalidateModule(mod);
149
+ server.hot.send({ type: "full-reload" });
150
+ }
151
+ },
152
+ async buildStart() {
153
+ if (contentConfig) return;
154
+ const root = resolvedConfig.root;
155
+ const configPath = path.resolve(root, options.config || CONFIG_FILE);
156
+ try {
157
+ contentConfig = (await import(pathToFileURL(configPath).href)).default;
158
+ watchedDirs = getWatchedDirs(contentConfig, root);
159
+ } catch {}
160
+ }
161
+ };
162
+ }
163
+
164
+ //#endregion
165
+ export { aeroContent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerobuilt/content",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Jamie Wilson",
@@ -11,32 +11,35 @@
11
11
  },
12
12
  "homepage": "https://github.com/aerobuilt/aero",
13
13
  "private": false,
14
- "types": "./dist/types.d.ts",
14
+ "types": "./dist/index.d.mts",
15
15
  "files": [
16
16
  "dist"
17
17
  ],
18
18
  "exports": {
19
19
  ".": {
20
- "types": "./dist/types.d.ts",
21
- "default": "./dist/index.js"
20
+ "types": "./dist/index.d.mts",
21
+ "default": "./dist/index.mjs"
22
22
  },
23
23
  "./vite": {
24
- "types": "./dist/vite.d.ts",
25
- "default": "./dist/vite.js"
24
+ "types": "./dist/vite.d.mts",
25
+ "default": "./dist/vite.mjs"
26
26
  },
27
27
  "./markdown": {
28
- "types": "./dist/markdown.d.ts",
29
- "default": "./dist/markdown.js"
28
+ "types": "./dist/markdown.d.mts",
29
+ "default": "./dist/markdown.mjs"
30
30
  },
31
31
  "./render": {
32
- "types": "./dist/render.d.ts",
33
- "default": "./dist/render.js"
32
+ "types": "./dist/render.d.mts",
33
+ "default": "./dist/render.mjs"
34
34
  },
35
35
  "./processor": {
36
- "types": "./dist/processor.d.ts",
37
- "default": "./dist/processor.js"
36
+ "types": "./dist/processor.d.mts",
37
+ "default": "./dist/processor.mjs"
38
38
  },
39
- "./types": "./dist/types.d.ts"
39
+ "./types": {
40
+ "types": "./dist/types.d.mts",
41
+ "default": "./dist/types.mjs"
42
+ }
40
43
  },
41
44
  "dependencies": {
42
45
  "fast-glob": "^3.3.3",
@@ -62,14 +65,14 @@
62
65
  "@shikijs/rehype": "^3.0.0",
63
66
  "@shikijs/transformers": "^3.0.0",
64
67
  "@types/node": "^25.3.0",
65
- "tsup": "^8.5.1",
68
+ "tsdown": "^0.21.0-beta.2",
66
69
  "typescript": "^5.9.3",
67
70
  "vite": "8.0.0-beta.15",
68
71
  "vitest": "^4.0.18",
69
72
  "zod": "^4.3.6"
70
73
  },
71
74
  "scripts": {
72
- "build": "tsup src/index.ts src/vite.ts src/markdown.ts src/render.ts src/processor.ts --format esm --dts --clean --out-dir dist",
75
+ "build": "tsdown src/index.ts src/types.ts src/vite.ts src/markdown.ts src/render.ts src/processor.ts --format esm --dts --clean --out-dir dist",
73
76
  "typecheck": "tsc --noEmit",
74
77
  "test": "vitest run"
75
78
  }
@@ -1,52 +0,0 @@
1
- // src/processor.ts
2
- import { remark } from "remark";
3
- import remarkRehype from "remark-rehype";
4
- import rehypeStringify from "rehype-stringify";
5
- if (!globalThis.__aeroProcessorState) {
6
- globalThis.__aeroProcessorState = {
7
- processor: null,
8
- initialized: false
9
- };
10
- }
11
- function applyPlugins(pipeline, plugins) {
12
- for (const entry of plugins) {
13
- if (Array.isArray(entry)) {
14
- const [plugin, ...options] = entry;
15
- pipeline = pipeline.use(plugin, ...options);
16
- } else {
17
- pipeline = pipeline.use(entry);
18
- }
19
- }
20
- return pipeline;
21
- }
22
- function createProcessor(remarkPlugins = [], rehypePlugins = []) {
23
- let pipeline = remark();
24
- pipeline = applyPlugins(pipeline, remarkPlugins);
25
- pipeline = pipeline.use(remarkRehype);
26
- pipeline = applyPlugins(pipeline, rehypePlugins);
27
- return pipeline.use(rehypeStringify);
28
- }
29
- async function initProcessor(config) {
30
- globalThis.__aeroProcessorState.processor = createProcessor(
31
- config?.remarkPlugins ?? [],
32
- config?.rehypePlugins ?? []
33
- );
34
- globalThis.__aeroProcessorState.initialized = true;
35
- }
36
- function getProcessor() {
37
- if (!globalThis.__aeroProcessorState.processor) {
38
- globalThis.__aeroProcessorState.processor = createProcessor();
39
- globalThis.__aeroProcessorState.initialized = true;
40
- }
41
- return globalThis.__aeroProcessorState.processor;
42
- }
43
- function resetProcessor() {
44
- globalThis.__aeroProcessorState.processor = null;
45
- globalThis.__aeroProcessorState.initialized = false;
46
- }
47
-
48
- export {
49
- initProcessor,
50
- getProcessor,
51
- resetProcessor
52
- };
@@ -1,17 +0,0 @@
1
- import {
2
- getProcessor
3
- } from "./chunk-3GJWVG5N.js";
4
-
5
- // src/render.ts
6
- async function render(doc) {
7
- if (!doc) {
8
- console.warn("[aero] render() received null or undefined document. Returning empty HTML.");
9
- return { html: "" };
10
- }
11
- const result = await getProcessor().process(doc.body);
12
- return { html: String(result) };
13
- }
14
-
15
- export {
16
- render
17
- };
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export { a as ContentCollectionConfig, b as ContentConfig, C as ContentDocument, c as ContentMeta, M as MarkdownConfig, d as defineCollection, e as defineConfig } from './types-D4g4HdAa.js';
2
- export { render } from './render.js';
3
- import 'unified';
4
- import 'zod';
package/dist/index.js DELETED
@@ -1,17 +0,0 @@
1
- import {
2
- render
3
- } from "./chunk-VZVJXSM5.js";
4
- import "./chunk-3GJWVG5N.js";
5
-
6
- // src/types.ts
7
- function defineCollection(config) {
8
- return config;
9
- }
10
- function defineConfig(config) {
11
- return config;
12
- }
13
- export {
14
- defineCollection,
15
- defineConfig,
16
- render
17
- };
@@ -1,21 +0,0 @@
1
- import { C as ContentDocument } from './types-D4g4HdAa.js';
2
- import 'unified';
3
- import 'zod';
4
-
5
- /**
6
- * Eager markdown-to-HTML compilation for use in collection transforms.
7
- *
8
- * @remarks
9
- * Delegates to the shared processor from `./processor`. For lazy rendering in pages,
10
- * use `render()` from `aero:content` instead.
11
- */
12
-
13
- /**
14
- * Compile a document's markdown body to HTML. Use in `transform` to attach `html` to each document.
15
- *
16
- * @param document - Content document (body is compiled).
17
- * @returns HTML string.
18
- */
19
- declare function compileMarkdown(document: ContentDocument): Promise<string>;
20
-
21
- export { compileMarkdown };
package/dist/markdown.js DELETED
@@ -1,12 +0,0 @@
1
- import {
2
- getProcessor
3
- } from "./chunk-3GJWVG5N.js";
4
-
5
- // src/markdown.ts
6
- async function compileMarkdown(document) {
7
- const result = await getProcessor().process(document.body);
8
- return String(result);
9
- }
10
- export {
11
- compileMarkdown
12
- };
package/dist/processor.js DELETED
@@ -1,10 +0,0 @@
1
- import {
2
- getProcessor,
3
- initProcessor,
4
- resetProcessor
5
- } from "./chunk-3GJWVG5N.js";
6
- export {
7
- getProcessor,
8
- initProcessor,
9
- resetProcessor
10
- };
package/dist/render.d.ts DELETED
@@ -1,23 +0,0 @@
1
- import { C as ContentDocument } from './types-D4g4HdAa.js';
2
- import 'unified';
3
- import 'zod';
4
-
5
- /**
6
- * Lazy markdown-to-HTML for use in pages (import from `aero:content`).
7
- *
8
- * @remarks
9
- * Delegates to the shared processor from `./processor`. Call from on:build with a document
10
- * (e.g. from getCollection); returns `{ html }`.
11
- */
12
-
13
- /**
14
- * Render a content document's markdown body to HTML. Use in pages with documents from getCollection.
15
- *
16
- * @param doc - Content document (or null/undefined; returns empty HTML and logs a warning).
17
- * @returns `{ html: string }`.
18
- */
19
- declare function render(doc: ContentDocument | null | undefined): Promise<{
20
- html: string;
21
- }>;
22
-
23
- export { render };
package/dist/render.js DELETED
@@ -1,7 +0,0 @@
1
- import {
2
- render
3
- } from "./chunk-VZVJXSM5.js";
4
- import "./chunk-3GJWVG5N.js";
5
- export {
6
- render
7
- };
@@ -1,88 +0,0 @@
1
- import { Pluggable } from 'unified';
2
- import { ZodType } from 'zod';
3
-
4
- /**
5
- * Content package types: document metadata, collection config, and define helpers.
6
- *
7
- * @remarks
8
- * Used by the loader (frontmatter + body → ContentDocument), by content.config.ts (defineCollection, defineConfig), and by the Vite plugin.
9
- */
10
-
11
- /** Metadata attached to every content document (path, slug, filename, extension). */
12
- interface ContentMeta {
13
- /** Path relative to the collection directory (no extension). */
14
- path: string;
15
- /** Slug derived from the filename (no extension). */
16
- slug: string;
17
- /** The raw filename including extension. */
18
- filename: string;
19
- /** The file extension (e.g. `'.md'`). */
20
- extension: string;
21
- }
22
- /** A content document: id, validated frontmatter (`data`), raw body, and `_meta`. */
23
- interface ContentDocument<TSchema extends Record<string, any> = Record<string, any>> {
24
- /** Unique identifier: collection-relative path without extension. */
25
- id: string;
26
- /** Validated frontmatter fields. */
27
- data: TSchema;
28
- /** Raw body content (everything after frontmatter). */
29
- body: string;
30
- /** Auto-generated metadata. */
31
- _meta: ContentMeta;
32
- }
33
- /** Single collection definition: name, directory, glob, optional schema and transform. */
34
- interface ContentCollectionConfig<TSchema extends Record<string, any> = Record<string, any>, TOutput = ContentDocument<TSchema>> {
35
- /** Unique collection name (used as the export key, e.g. `allDocs`). */
36
- name: string;
37
- /** Directory to scan, relative to project root. */
38
- directory: string;
39
- /** Glob pattern for files to include (default: `**\/*.md`). */
40
- include?: string;
41
- /** Zod schema for frontmatter validation; `body` is always present. */
42
- schema?: ZodType<TSchema>;
43
- /** Optional async transform after validation; receives document, returns final shape. */
44
- transform?: (document: ContentDocument<TSchema>) => TOutput | Promise<TOutput>;
45
- }
46
- /**
47
- * Markdown pipeline configuration: custom remark and rehype plugins.
48
- *
49
- * @remarks
50
- * The pipeline is: `remark` -> `[remarkPlugins]` -> `remark-rehype` -> `[rehypePlugins]` -> `rehype-stringify`.
51
- * Add syntax highlighting (e.g. `@shikijs/rehype`, `rehype-pretty-code`) as a rehype plugin.
52
- */
53
- interface MarkdownConfig {
54
- /** Remark plugins to apply before the remark-to-rehype bridge. */
55
- remarkPlugins?: Pluggable[];
56
- /** Rehype plugins to apply after remark-rehype and before rehype-stringify. */
57
- rehypePlugins?: Pluggable[];
58
- }
59
- /** Top-level content config: array of collection definitions with optional markdown pipeline plugins. */
60
- interface ContentConfig {
61
- collections: ContentCollectionConfig<any, any>[];
62
- /**
63
- * Custom remark and rehype plugins for the markdown pipeline.
64
- *
65
- * @remarks
66
- * The pipeline is always rehype-based:
67
- * `remark` -> `[remarkPlugins]` -> `remark-rehype` -> `[rehypePlugins]` -> `rehype-stringify`.
68
- * Without any rehype plugins, code blocks render as plain `<pre><code>`.
69
- * Add `@shikijs/rehype` or `rehype-pretty-code` as a rehype plugin for syntax highlighting.
70
- */
71
- markdown?: MarkdownConfig;
72
- }
73
- /**
74
- * Define a content collection (typed helper for content.config.ts).
75
- *
76
- * @param config - Collection config (name, directory, include, schema, transform).
77
- * @returns The same config (unchanged).
78
- */
79
- declare function defineCollection<TSchema extends Record<string, any> = Record<string, any>, TOutput = ContentDocument<TSchema>>(config: ContentCollectionConfig<TSchema, TOutput>): ContentCollectionConfig<TSchema, TOutput>;
80
- /**
81
- * Define the content config (typed helper for content.config.ts).
82
- *
83
- * @param config - Config with `collections` array and optional `markdown` pipeline plugins.
84
- * @returns The same config (unchanged).
85
- */
86
- declare function defineConfig(config: ContentConfig): ContentConfig;
87
-
88
- export { type ContentDocument as C, type MarkdownConfig as M, type ContentCollectionConfig as a, type ContentConfig as b, type ContentMeta as c, defineCollection as d, defineConfig as e };
package/dist/vite.js DELETED
@@ -1,177 +0,0 @@
1
- import {
2
- initProcessor
3
- } from "./chunk-3GJWVG5N.js";
4
-
5
- // src/loader.ts
6
- import fg from "fast-glob";
7
- import matter from "gray-matter";
8
- import fs from "fs";
9
- import path from "path";
10
- async function loadCollection(config, root) {
11
- const dir = path.resolve(root, config.directory);
12
- const pattern = config.include || "**/*.md";
13
- const files = await fg(pattern, { cwd: dir, absolute: true });
14
- const documents = [];
15
- for (const file of files) {
16
- const raw = fs.readFileSync(file, "utf-8");
17
- const { data: frontmatter, content: body } = matter(raw);
18
- const relPath = path.relative(dir, file);
19
- const parsed = path.parse(relPath);
20
- const id = parsed.dir ? `${parsed.dir}/${parsed.name}` : parsed.name;
21
- const meta = {
22
- path: id,
23
- slug: parsed.name,
24
- filename: parsed.base,
25
- extension: parsed.ext
26
- };
27
- let validated = frontmatter;
28
- if (config.schema) {
29
- const result = config.schema.safeParse(frontmatter);
30
- if (!result.success) {
31
- const errors = "error" in result ? result.error?.issues?.map((i) => i.message).join(", ") : "Validation failed";
32
- console.warn(
33
- `[aero:content] \u26A0 Skipping "${relPath}" in collection "${config.name}": ${errors}`
34
- );
35
- continue;
36
- }
37
- validated = result.data;
38
- }
39
- const doc = {
40
- id,
41
- data: validated,
42
- body,
43
- _meta: meta
44
- };
45
- if (config.transform) {
46
- documents.push(await config.transform(doc));
47
- } else {
48
- documents.push(doc);
49
- }
50
- }
51
- return documents;
52
- }
53
- async function loadAllCollections(config, root) {
54
- const result = /* @__PURE__ */ new Map();
55
- for (const collection of config.collections) {
56
- const docs = await loadCollection(collection, root);
57
- result.set(collection.name, docs);
58
- }
59
- return result;
60
- }
61
- function getWatchedDirs(config, root) {
62
- return config.collections.map((c) => path.resolve(root, c.directory));
63
- }
64
- function serializeContentModule(loaded) {
65
- const collectionsContent = Array.from(loaded.entries()).map(([name, docs]) => ` ${JSON.stringify(name)}: ${JSON.stringify(docs, null, 2)}`).join(",\n");
66
- return `
67
- const __collections = {
68
- ${collectionsContent}
69
- };
70
-
71
- export function getCollection(name, filterFn) {
72
- let data = __collections[name];
73
- if (!data) throw new Error(\`[aero:content] Collection "\${name}" not found. Available: \${Object.keys(__collections).join(", ")}\`);
74
-
75
- if (import.meta.env.PROD) {
76
- data = data.filter(item => item.data.published === true);
77
- }
78
-
79
- if (typeof filterFn === "function") {
80
- return data.filter(filterFn);
81
- }
82
-
83
- return data;
84
- }
85
-
86
- export { render } from '@aerobuilt/content/render';
87
- `;
88
- }
89
-
90
- // src/vite.ts
91
- import path2 from "path";
92
- import { pathToFileURL } from "url";
93
- var CONTENT_MODULE_ID = "aero:content";
94
- var RESOLVED_CONTENT_MODULE_ID = "\0aero:content";
95
- var CONFIG_FILE = "content.config.ts";
96
- function aeroContent(options = {}) {
97
- let resolvedConfig;
98
- let contentConfig = null;
99
- let serialized = null;
100
- let watchedDirs = [];
101
- return {
102
- name: "vite-plugin-aero-content",
103
- /** Load content.config.ts, set contentConfig and watchedDirs; initialize processor early. */
104
- async configResolved(config) {
105
- resolvedConfig = config;
106
- const root = config.root;
107
- const configPath = path2.resolve(root, options.config || CONFIG_FILE);
108
- try {
109
- const configUrl = pathToFileURL(configPath).href;
110
- const mod = await import(
111
- /* @vite-ignore */
112
- configUrl
113
- );
114
- contentConfig = mod.default;
115
- watchedDirs = getWatchedDirs(contentConfig, root);
116
- await initProcessor(contentConfig.markdown);
117
- } catch (err) {
118
- if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "ENOENT") {
119
- config.logger.warn(
120
- `[aero:content] No config found at "${configPath}". Content collections disabled.`
121
- );
122
- return;
123
- }
124
- throw err;
125
- }
126
- },
127
- /** Resolve aero:content and aero:content/… to the resolved virtual ID. */
128
- resolveId(id) {
129
- if (id === CONTENT_MODULE_ID) {
130
- return RESOLVED_CONTENT_MODULE_ID;
131
- }
132
- if (id.startsWith(CONTENT_MODULE_ID + "/")) {
133
- return RESOLVED_CONTENT_MODULE_ID;
134
- }
135
- return null;
136
- },
137
- /** Load virtual module: loadAllCollections, serializeContentModule, return ESM source. */
138
- async load(id) {
139
- if (id !== RESOLVED_CONTENT_MODULE_ID) return null;
140
- if (!contentConfig) {
141
- return "// aero:content \u2014 no collections configured\n";
142
- }
143
- const loaded = await loadAllCollections(contentConfig, resolvedConfig.root);
144
- serialized = serializeContentModule(loaded);
145
- return serialized;
146
- },
147
- /** Invalidate virtual module and full-reload when a file in a watched content dir changes. */
148
- handleHotUpdate({ file, server }) {
149
- const isContent = watchedDirs.some((dir) => file.startsWith(dir));
150
- if (!isContent) return;
151
- const mod = server.moduleGraph.getModuleById(RESOLVED_CONTENT_MODULE_ID);
152
- if (mod) {
153
- server.moduleGraph.invalidateModule(mod);
154
- server.hot.send({ type: "full-reload" });
155
- }
156
- },
157
- /** Fallback: load config in build if not already loaded in configResolved. */
158
- async buildStart() {
159
- if (contentConfig) return;
160
- const root = resolvedConfig.root;
161
- const configPath = path2.resolve(root, options.config || CONFIG_FILE);
162
- try {
163
- const configUrl = pathToFileURL(configPath).href;
164
- const mod = await import(
165
- /* @vite-ignore */
166
- configUrl
167
- );
168
- contentConfig = mod.default;
169
- watchedDirs = getWatchedDirs(contentConfig, root);
170
- } catch {
171
- }
172
- }
173
- };
174
- }
175
- export {
176
- aeroContent
177
- };