@takazudo/zfb 0.1.0-next.10

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +207 -0
  4. package/bin/zfb.mjs +82 -0
  5. package/dist/config.d.ts +542 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +24 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/content.d.ts +240 -0
  10. package/dist/content.d.ts.map +1 -0
  11. package/dist/content.js +460 -0
  12. package/dist/content.js.map +1 -0
  13. package/dist/frontmatter.d.ts +23 -0
  14. package/dist/frontmatter.d.ts.map +1 -0
  15. package/dist/frontmatter.js +142 -0
  16. package/dist/frontmatter.js.map +1 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +21 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/island.d.ts +121 -0
  22. package/dist/island.d.ts.map +1 -0
  23. package/dist/island.js +273 -0
  24. package/dist/island.js.map +1 -0
  25. package/dist/jsx-types.d.ts +37 -0
  26. package/dist/jsx-types.d.ts.map +1 -0
  27. package/dist/jsx-types.js +12 -0
  28. package/dist/jsx-types.js.map +1 -0
  29. package/dist/paginate.d.ts +43 -0
  30. package/dist/paginate.d.ts.map +1 -0
  31. package/dist/paginate.js +44 -0
  32. package/dist/paginate.js.map +1 -0
  33. package/dist/plugins.d.ts +259 -0
  34. package/dist/plugins.d.ts.map +1 -0
  35. package/dist/plugins.js +42 -0
  36. package/dist/plugins.js.map +1 -0
  37. package/dist/runtime.d.ts +101 -0
  38. package/dist/runtime.d.ts.map +1 -0
  39. package/dist/runtime.js +454 -0
  40. package/dist/runtime.js.map +1 -0
  41. package/dist/types.d.ts +27 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +33 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +98 -0
@@ -0,0 +1,460 @@
1
+ // `zfb/content` — minimal v0 content collection loader.
2
+ //
3
+ // Reads `*.md` files from a content collection directory, parses YAML
4
+ // frontmatter, and returns typed entries. This is a deliberately small
5
+ // stub so the bundled basic-blog template can call `getCollection("blog")` today;
6
+ // the production path lives in `crates/zfb-content` and will replace this
7
+ // once the JS-runtime decision (ADR-001) lands and the renderer wires the
8
+ // Rust pipeline back through to user code.
9
+ //
10
+ // Scope (v0):
11
+ // - YAML-ish frontmatter only: `key: value`, plus `key:\n - item` arrays.
12
+ // Quoted strings are unwrapped. ISO dates stay as strings.
13
+ // - Body is the post content **after** the closing `---`, returned as raw
14
+ // text. This is intentionally NOT pre-rendered HTML: the markdown
15
+ // pipeline lives in the Rust crate and the JS stub does not duplicate
16
+ // it.
17
+ // - Collection root is resolved from
18
+ // `process.env.ZFB_CONTENT_ROOT` (set by the dev/build pipeline), or
19
+ // `<cwd>/content` as a fallback for unit tests and direct invocation.
20
+ //
21
+ // TODO(zfb-content): swap this stub for the runtime-provided implementation
22
+ // once the content engine ships end-to-end.
23
+ import { parseFrontmatter } from "./frontmatter.js";
24
+ // Re-export the parser surface so existing `zfb/content` consumers that
25
+ // import `parseFrontmatter` / `ParsedFrontmatter` from the content
26
+ // subpath keep working. The implementation now lives in `./frontmatter.ts`
27
+ // (BCI-3 fs-free subpath) — this re-export is the bridge for callers
28
+ // that have not migrated yet.
29
+ export { parseFrontmatter };
30
+ /**
31
+ * Register a [`Snapshot`] so [`getCollection`] resolves from memory.
32
+ *
33
+ * Pass `undefined` to clear (used by tests that need to restore the v0
34
+ * filesystem path between runs). Idempotent: the latest call wins.
35
+ *
36
+ * Stored on `globalThis.__zfb.contentSnapshot` rather than a
37
+ * module-level `let` so a worker bundle that ends up with two
38
+ * `zfb/content` module instances still sees a single shared snapshot —
39
+ * see the [`SnapshotBridgeNamespace`] doc above for the full
40
+ * pnpm-symlink rationale.
41
+ */
42
+ export function setContentSnapshot(snapshot) {
43
+ const g = globalThis;
44
+ const ns = (g.__zfb ?? {});
45
+ ns.contentSnapshot = snapshot;
46
+ g.__zfb = ns;
47
+ }
48
+ /**
49
+ * Read the currently-installed [`Snapshot`], or `undefined` if none is
50
+ * registered. Exposed mostly for tests; production callers should not
51
+ * need to introspect the bridge state.
52
+ *
53
+ * Reads from `globalThis.__zfb.contentSnapshot`; see
54
+ * [`setContentSnapshot`] for why the slot lives on `globalThis`.
55
+ */
56
+ export function getContentSnapshot() {
57
+ return globalThis.__zfb?.contentSnapshot;
58
+ }
59
+ // Cached node:fs / node:path module references. Populated lazily on first
60
+ // fs-path use (see [`loadNodeModules`]); reused on subsequent calls.
61
+ let cachedNodeFs;
62
+ let cachedNodePath;
63
+ /**
64
+ * Synchronously load `node:fs` and `node:path`, caching the results.
65
+ *
66
+ * The node specifiers are concatenated at runtime (`"node:" + "fs"`) so
67
+ * esbuild's static analyzer cannot follow them — that's the load-bearing
68
+ * detail here, because this module is reachable from browser-bundled
69
+ * island chains via the `@takazudo/zfb` root barrel (see top-of-file note).
70
+ *
71
+ * Uses CommonJS `require` via [`createRequire`] (stable, sync) rather than
72
+ * `await import()` (async, would force `getCollection` async and violate
73
+ * ADR-004). `createRequire` itself is fetched from `node:module` through
74
+ * the same runtime-built specifier pattern.
75
+ *
76
+ * If `createRequire` cannot be obtained at all (i.e. truly running in a
77
+ * browser-shaped runtime — which would mean a misconfigured island
78
+ * bundle), throws so the failure is loud rather than silent.
79
+ */
80
+ function loadNodeModules() {
81
+ if (cachedNodeFs !== undefined && cachedNodePath !== undefined) {
82
+ return { fs: cachedNodeFs, path: cachedNodePath };
83
+ }
84
+ // Runtime-built specifiers: opaque to esbuild's static analyzer.
85
+ const moduleSpecifier = "node:" + "module";
86
+ const fsSpecifier = "node:" + "fs";
87
+ const pathSpecifier = "node:" + "path";
88
+ // Strategy A: prefer the host `require` from a CommonJS context. We
89
+ // probe via `globalThis` and `Function`-built lookup so neither esbuild
90
+ // nor stricter ESM tooling errors out at the lookup site.
91
+ const dynamicGlobal = globalThis;
92
+ let nodeRequire = dynamicGlobal.require;
93
+ // Strategy B: ESM context — synthesize a require via `node:module`'s
94
+ // `createRequire`. Loading `node:module` itself through the same
95
+ // dynamic specifier shields it from esbuild's static walker.
96
+ if (typeof nodeRequire !== "function") {
97
+ // `Function("return require")()` returns the enclosing `require` when
98
+ // the bundler/loader injects one (Node CJS, esbuild default). Falls
99
+ // through if undefined — caught below.
100
+ try {
101
+ nodeRequire = new Function("return typeof require === 'function' ? require : undefined")();
102
+ }
103
+ catch {
104
+ nodeRequire = undefined;
105
+ }
106
+ }
107
+ if (typeof nodeRequire !== "function") {
108
+ // Last resort: synthesize via createRequire. Reaches `node:module`
109
+ // through a dynamic require we have to bootstrap somehow — the only
110
+ // way without a static `import` is `process.getBuiltinModule` (Node
111
+ // 22+) which exposes built-ins synchronously without a require.
112
+ const proc = globalThis.process;
113
+ const getBuiltin = proc?.getBuiltinModule;
114
+ if (typeof getBuiltin === "function") {
115
+ const mod = getBuiltin(moduleSpecifier);
116
+ nodeRequire = mod.createRequire(import.meta.url);
117
+ }
118
+ }
119
+ if (typeof nodeRequire !== "function") {
120
+ throw new Error("zfb/content: cannot load node:fs / node:path — no Node-style require available. " +
121
+ "This module's filesystem path requires a Node runtime; if you see this in a browser " +
122
+ "bundle, the bundler should externalize node:* imports (the islands bundler does so).");
123
+ }
124
+ cachedNodeFs = nodeRequire(fsSpecifier);
125
+ cachedNodePath = nodeRequire(pathSpecifier);
126
+ return { fs: cachedNodeFs, path: cachedNodePath };
127
+ }
128
+ /**
129
+ * Resolve the directory that holds a named content collection. Override
130
+ * via `ZFB_CONTENT_ROOT` so tests / fixtures can point at an arbitrary
131
+ * directory.
132
+ */
133
+ function resolveCollectionDir(name) {
134
+ const { path } = loadNodeModules();
135
+ const envRoot = process.env["ZFB_CONTENT_ROOT"];
136
+ const root = envRoot ? path.resolve(envRoot) : path.resolve(process.cwd(), "content");
137
+ return path.join(root, name);
138
+ }
139
+ /**
140
+ * Build the v0 stub's bridge specifier for an entry. Mirrors the Rust-side
141
+ * convention (`mdx://<collection>/<slug>`) minus the body hash — the JS
142
+ * stub does not compile MDX, so it has no hash to attach. The bridge
143
+ * resolver on the renderer side is responsible for matching either form.
144
+ */
145
+ function buildModuleSpecifier(collection, slug) {
146
+ return `mdx://${collection}/${slug}`;
147
+ }
148
+ /**
149
+ * Build the `Content` component for an entry. Captures `module_specifier`
150
+ * + `body` in the closure so the returned function takes only `props`.
151
+ *
152
+ * The bridge lookup is done lazily on every call (not at entry-construction
153
+ * time) so the renderer can install / swap `globalThis.__zfb.content` at
154
+ * any point before the first render without ordering hazards.
155
+ */
156
+ function buildContentComponent(module_specifier, body) {
157
+ return function Content(props) {
158
+ const bridge = globalThis.__zfb?.content;
159
+ const renderer = bridge?.get(module_specifier);
160
+ if (typeof renderer === "function") {
161
+ // Trust the bridge to return a JSX-element-shaped value — we don't
162
+ // try to validate; both Preact and React JSX runtimes accept any
163
+ // structural `{ type, props, key }` object on either side of the
164
+ // boundary, and the renderer is the source of truth here.
165
+ return renderer(props);
166
+ }
167
+ return renderFallback(body);
168
+ };
169
+ }
170
+ /**
171
+ * Stamp the `constructor: undefined` sentinel on a structural JSX-element
172
+ * shape so `preact-render-to-string` (and Preact's diff path) treat it
173
+ * as a real VNode. Without this, Preact reads `.constructor` as `Object`
174
+ * — the value inherited from the literal — and silently drops the node,
175
+ * which is what produced the empty MDX bodies tracked in zudo-doc#505.
176
+ *
177
+ * Same trick `Island`'s `makeVNode` uses; kept private here so callers
178
+ * keep treating `ContentElement` / `ContentComponentElement` as opaque.
179
+ */
180
+ function stampVNode(shape) {
181
+ shape.constructor = undefined;
182
+ return shape;
183
+ }
184
+ /**
185
+ * Build the structural JSX element returned when the bridge is absent.
186
+ *
187
+ * Shape: `<pre data-zfb-content-fallback>{marker}\n{body}</pre>` — the
188
+ * leading `[zfb fallback render]` marker line is part of the public
189
+ * fallback contract (it's both a visual signal and a grep target). Tests
190
+ * pin both the attribute and the marker line.
191
+ */
192
+ function renderFallback(body) {
193
+ return stampVNode({
194
+ type: "pre",
195
+ props: {
196
+ "data-zfb-content-fallback": "",
197
+ children: `${FALLBACK_MARKER}\n${body}`,
198
+ },
199
+ key: null,
200
+ });
201
+ }
202
+ /** Leading marker line emitted by [`renderFallback`]. Public contract. */
203
+ const FALLBACK_MARKER = "[zfb fallback render]";
204
+ /**
205
+ * Load every `*.md` file in the named collection. Files starting with `.`
206
+ * or that lack a `.md` extension are ignored.
207
+ *
208
+ * **ADR-004 contract: this function is synchronous.** TSX page modules
209
+ * call it from anywhere — top-level, inside a render body, inside a
210
+ * `useMemo` — and SSR completes in a single pass without yielding. The
211
+ * snapshot path returns from memory; the filesystem fallback uses sync
212
+ * `node:fs` APIs so the surface stays unified. (The legacy async
213
+ * implementation was an oversight — the ADR predates it; SSG paths
214
+ * always saw a Promise where ADR-004 says they should see an array,
215
+ * which is why migrations from Astro tripped on `getCollection().filter
216
+ * is not a function`.)
217
+ *
218
+ * @example
219
+ * const posts = getCollection<{ title: string; date: string }>("blog");
220
+ */
221
+ export function getCollection(name) {
222
+ // Snapshot path: installed by `@takazudo/zfb-runtime`'s
223
+ // `createPageRouter` at Worker boot. Worker runtimes have no `fs`, so
224
+ // this branch is the production path under the embedded V8 host.
225
+ //
226
+ // The snapshot lookup reads `globalThis.__zfb.contentSnapshot`
227
+ // (see `setContentSnapshot` above) rather than a per-module slot so
228
+ // the cross-`zfb/content`-instance case under `--preserve-symlinks`
229
+ // resolves through the same shared state — see #449.
230
+ const installedSnapshot = globalThis.__zfb?.contentSnapshot;
231
+ if (installedSnapshot !== undefined) {
232
+ const list = installedSnapshot.collections[name] ?? [];
233
+ return list.map((entry) => entryFromSnapshot(entry));
234
+ }
235
+ // Filesystem fallback (v0 path). Used by unit tests and direct Node
236
+ // invocations outside the Worker bundle.
237
+ //
238
+ // BCI-6: traversal is now recursive — subdirectories are walked so a
239
+ // collection rooted at `content/blog/` can contain nested `*.md` files
240
+ // (e.g. `content/blog/2024/hello.md`). Slugs are derived from the
241
+ // relative path so callers get stable, unique identifiers across nesting
242
+ // levels.
243
+ const dir = resolveCollectionDir(name);
244
+ let mdPaths;
245
+ try {
246
+ mdPaths = collectMdFilesSync(dir);
247
+ }
248
+ catch (err) {
249
+ // Guard the `code` access at runtime — a thrown non-`Error` value
250
+ // (rare, but possible) would otherwise crash here. We only swallow
251
+ // a true ENOENT; anything else propagates.
252
+ if (err !== null &&
253
+ typeof err === "object" &&
254
+ "code" in err &&
255
+ err.code === "ENOENT") {
256
+ return [];
257
+ }
258
+ throw err;
259
+ }
260
+ const { fs, path } = loadNodeModules();
261
+ return mdPaths.map((fullPath) => {
262
+ const raw = fs.readFileSync(fullPath, "utf8");
263
+ const { data, body } = parseFrontmatter(raw);
264
+ // Derive a stable slug from the relative path (relative to collection
265
+ // root), stripping the `.md` extension. For top-level files this
266
+ // produces the same value as before; for nested files it produces a
267
+ // path-based slug (e.g. `2024/hello`).
268
+ const rel = path.relative(dir, fullPath);
269
+ const slug = _relPathToSlug(rel);
270
+ const module_specifier = buildModuleSpecifier(name, slug);
271
+ return {
272
+ slug,
273
+ data: data,
274
+ body,
275
+ module_specifier,
276
+ Content: buildContentComponent(module_specifier, body),
277
+ };
278
+ });
279
+ }
280
+ /**
281
+ * Construct a [`CollectionEntry`] from a [`SnapshotEntry`]. The snapshot
282
+ * carries `frontmatter` as a possibly-`null` JSON value (matches the
283
+ * Rust contract for entries with no frontmatter); we normalise `null` /
284
+ * `undefined` to an empty object so consumers' `.data.title` reads
285
+ * never have to deal with `null`.
286
+ *
287
+ * **Type-safety note:** `T` is the caller-supplied frontmatter shape
288
+ * but we do **not** validate it at runtime — if the page declares a
289
+ * shape that the actual frontmatter doesn't match, the cast below
290
+ * lies. Callers are expected to keep their `getCollection<MySchema>()`
291
+ * generic in sync with the actual frontmatter; we acknowledge the
292
+ * unsafety with the explicit `unknown` indirection rather than a
293
+ * direct (and silently lossy) cast.
294
+ */
295
+ function entryFromSnapshot(entry) {
296
+ const data = entry.frontmatter === null || entry.frontmatter === undefined
297
+ ? {}
298
+ : entry.frontmatter;
299
+ return {
300
+ slug: entry.slug,
301
+ data,
302
+ body: entry.body,
303
+ module_specifier: entry.module_specifier,
304
+ Content: buildContentComponent(entry.module_specifier, entry.body),
305
+ };
306
+ }
307
+ /**
308
+ * Recursively collect every `*.md` file under `dir` (synchronous).
309
+ *
310
+ * BCI-6: replaces the old flat `readdir(dir).filter(n => n.endsWith(".md"))`
311
+ * approach. Hidden files (names starting with `.`) and hidden directories
312
+ * are skipped at every nesting level, matching the top-level behaviour of
313
+ * the previous implementation.
314
+ *
315
+ * Returns absolute paths sorted lexicographically so the result order is
316
+ * deterministic across platforms and Node versions.
317
+ *
318
+ * Synchronous to honour ADR-004 — see [`getCollection`].
319
+ */
320
+ function collectMdFilesSync(dir) {
321
+ const result = [];
322
+ const { fs, path } = loadNodeModules();
323
+ walkDirSync(fs, path, dir, result);
324
+ result.sort();
325
+ return result;
326
+ }
327
+ function walkDirSync(fs, path, current, out) {
328
+ const entries = fs.readdirSync(current, { withFileTypes: true });
329
+ for (const entry of entries) {
330
+ if (entry.name.startsWith("."))
331
+ continue;
332
+ const fullPath = path.join(current, entry.name);
333
+ // Skip symlinks to avoid infinite loops caused by cycles (e.g. a symlink
334
+ // pointing at a parent directory). Content files are expected to be plain
335
+ // regular files; following symlinks provides no value here.
336
+ if (entry.isSymbolicLink())
337
+ continue;
338
+ if (entry.isDirectory()) {
339
+ walkDirSync(fs, path, fullPath, out);
340
+ }
341
+ else if (entry.isFile() && entry.name.endsWith(".md")) {
342
+ out.push(fullPath);
343
+ }
344
+ }
345
+ }
346
+ /**
347
+ * @internal
348
+ *
349
+ * Convert a `path.relative()` result into a forward-slash-separated
350
+ * slug with the trailing `.md` extension stripped.
351
+ *
352
+ * Slugs are URL-flavored identifiers, not filesystem paths — they
353
+ * MUST use `/` regardless of the host OS so a nested entry like
354
+ * `2024/hello.md` produces the slug `2024/hello` on both POSIX and
355
+ * Windows. Without this normalisation, Windows callers would see
356
+ * `2024\hello`, which then leaks through to `module_specifier` and
357
+ * any URL the consumer derives from the slug.
358
+ *
359
+ * Exported solely so the unit test suite can pin the Windows
360
+ * behaviour without needing an actual Windows host. Do not depend on
361
+ * this from application code — name and signature may change.
362
+ */
363
+ export function _relPathToSlug(relPath) {
364
+ const { path } = loadNodeModules();
365
+ const posix = path.sep === "/" ? relPath : relPath.split(path.sep).join("/");
366
+ // Some Node versions normalise `\` even when sep is `/`, so be
367
+ // defensive: collapse any straggling backslashes too.
368
+ const normalised = posix.includes("\\") ? posix.split("\\").join("/") : posix;
369
+ return normalised.endsWith(".md") ? normalised.slice(0, -".md".length) : normalised;
370
+ }
371
+ /** Internal helper: build a structural JSX element of the given tag. */
372
+ function buildOverrideElement(tag, props) {
373
+ const { children, ...rest } = props;
374
+ // `stampVNode` sets `constructor: undefined` so Preact recognises the
375
+ // returned object as a VNode rather than foreign data — see the helper's
376
+ // docblock for context (zudo-doc#505).
377
+ return stampVNode({
378
+ type: tag,
379
+ props: { ...rest, children },
380
+ key: null,
381
+ });
382
+ }
383
+ /**
384
+ * `<h2>` passthrough override. Ported from zudo-doc's `HeadingH2`, stripped
385
+ * of styling — v0 ships pass-through behaviour; visual treatment is layered
386
+ * on by the consumer (or by a follow-up enhancement pass).
387
+ */
388
+ export function ContentH2(props) {
389
+ return buildOverrideElement("h2", props);
390
+ }
391
+ /** `<h3>` passthrough override. See [`ContentH2`] for the contract. */
392
+ export function ContentH3(props) {
393
+ return buildOverrideElement("h3", props);
394
+ }
395
+ /** `<h4>` passthrough override. See [`ContentH2`] for the contract. */
396
+ export function ContentH4(props) {
397
+ return buildOverrideElement("h4", props);
398
+ }
399
+ /** `<p>` passthrough override. Mirrors zudo-doc's `ContentParagraph`. */
400
+ export function ContentParagraph(props) {
401
+ return buildOverrideElement("p", props);
402
+ }
403
+ /** `<a>` passthrough override. Mirrors zudo-doc's `ContentLink`. */
404
+ export function ContentLink(props) {
405
+ return buildOverrideElement("a", props);
406
+ }
407
+ /** `<strong>` passthrough override. Mirrors zudo-doc's `ContentStrong`. */
408
+ export function ContentStrong(props) {
409
+ return buildOverrideElement("strong", props);
410
+ }
411
+ /** `<blockquote>` passthrough override. Mirrors zudo-doc's `ContentBlockquote`. */
412
+ export function ContentBlockquote(props) {
413
+ return buildOverrideElement("blockquote", props);
414
+ }
415
+ /** `<ul>` passthrough override. Mirrors zudo-doc's `ContentUl`. */
416
+ export function ContentUl(props) {
417
+ return buildOverrideElement("ul", props);
418
+ }
419
+ /** `<ol>` passthrough override. Mirrors zudo-doc's `ContentOl`. */
420
+ export function ContentOl(props) {
421
+ return buildOverrideElement("ol", props);
422
+ }
423
+ /** `<table>` passthrough override. Mirrors zudo-doc's `ContentTable`. */
424
+ export function ContentTable(props) {
425
+ return buildOverrideElement("table", props);
426
+ }
427
+ /** `<code>` passthrough override. Mirrors zudo-doc's `ContentCode`. */
428
+ export function ContentCode(props) {
429
+ return buildOverrideElement("code", props);
430
+ }
431
+ /**
432
+ * Default per-element override map — eleven entries covering the markdown
433
+ * tags the zudo-doc convention overrides (`h2`, `h3`, `h4`, `p`, `a`,
434
+ * `strong`, `blockquote`, `ul`, `ol`, `table`, `code`).
435
+ *
436
+ * `h1` is intentionally absent: page titles render from frontmatter, per
437
+ * the zudo-doc convention.
438
+ *
439
+ * Spread into a `components` prop to compose with custom overrides:
440
+ *
441
+ * ```tsx
442
+ * import { defaultComponents } from "zfb";
443
+ *
444
+ * <entry.Content components={{ ...defaultComponents, h2: MyFancyH2 }} />
445
+ * ```
446
+ */
447
+ export const defaultComponents = {
448
+ h2: ContentH2,
449
+ h3: ContentH3,
450
+ h4: ContentH4,
451
+ p: ContentParagraph,
452
+ a: ContentLink,
453
+ strong: ContentStrong,
454
+ blockquote: ContentBlockquote,
455
+ ul: ContentUl,
456
+ ol: ContentOl,
457
+ table: ContentTable,
458
+ code: ContentCode,
459
+ };
460
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /** Public shape returned by [`parseFrontmatter`]. */
2
+ export type ParsedFrontmatter = {
3
+ data: Record<string, unknown>;
4
+ body: string;
5
+ };
6
+ /**
7
+ * Parse a leading YAML-ish frontmatter block off a markdown document.
8
+ *
9
+ * **Public SDK surface.** Re-exported from `zfb/content` so consumers can
10
+ * write their own custom content loaders without re-implementing the
11
+ * (deliberately minimal) v0 frontmatter parser. The accepted grammar is
12
+ * documented at the top of this module.
13
+ *
14
+ * Handles:
15
+ * - empty frontmatter (`---\n---\nbody`) → `{ data: {}, body: "body" }`
16
+ * - file ending exactly with `---` (no trailing newline) → frontmatter
17
+ * parsed, body is empty.
18
+ *
19
+ * Returns `{ data: {}, body: <input> }` unchanged when no frontmatter
20
+ * fence is present, or when the opening fence has no matching closer.
21
+ */
22
+ export declare function parseFrontmatter(raw: string): ParsedFrontmatter;
23
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AA4BA,qDAAqD;AACrD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CA2C/D"}
@@ -0,0 +1,142 @@
1
+ // `zfb/frontmatter` — public subpath for the frontmatter parser.
2
+ //
3
+ // BCI-3: `parseFrontmatter` lives in this dedicated subpath so consumers
4
+ // that only need frontmatter parsing can import it without pulling in
5
+ // `zfb/content`'s Node `fs` transitive dependency chain. Workers / edge
6
+ // bundlers reading user content from a CMS or KV store can therefore
7
+ // use this parser without bringing `node:fs/promises` into their bundle.
8
+ //
9
+ // The implementation lives directly in this module — `zfb/content`
10
+ // imports from here, not the other way around. Tests pin the absence of
11
+ // transitive `node:fs*` imports so a regression that re-introduces the
12
+ // fs dependency surfaces immediately.
13
+ //
14
+ // ---------------------------------------------------------------------------
15
+ // Minimal frontmatter parser. Intentionally NOT a full YAML implementation —
16
+ // the v0 surface accepts the subset documented below. This avoids pulling
17
+ // in `gray-matter` or `js-yaml` for what is, in v0, three field types:
18
+ //
19
+ // - `key: value` scalar (quoted strings unwrapped, ISO dates kept as strings)
20
+ // - `key:` followed by a block list of `- item` lines
21
+ // - blank lines and `#` comment lines are ignored
22
+ //
23
+ // Returns `{ data: {}, body: <input> }` unchanged when no frontmatter
24
+ // fence is present, or when the opening fence has no matching closer.
25
+ // ---------------------------------------------------------------------------
26
+ const FRONTMATTER_DELIM = "---";
27
+ /**
28
+ * Parse a leading YAML-ish frontmatter block off a markdown document.
29
+ *
30
+ * **Public SDK surface.** Re-exported from `zfb/content` so consumers can
31
+ * write their own custom content loaders without re-implementing the
32
+ * (deliberately minimal) v0 frontmatter parser. The accepted grammar is
33
+ * documented at the top of this module.
34
+ *
35
+ * Handles:
36
+ * - empty frontmatter (`---\n---\nbody`) → `{ data: {}, body: "body" }`
37
+ * - file ending exactly with `---` (no trailing newline) → frontmatter
38
+ * parsed, body is empty.
39
+ *
40
+ * Returns `{ data: {}, body: <input> }` unchanged when no frontmatter
41
+ * fence is present, or when the opening fence has no matching closer.
42
+ */
43
+ export function parseFrontmatter(raw) {
44
+ // Strip a leading BOM and normalise line endings before splitting.
45
+ const text = raw.replace(/^/, "").replace(/\r\n/g, "\n");
46
+ if (!text.startsWith(`${FRONTMATTER_DELIM}\n`)) {
47
+ return { data: {}, body: text };
48
+ }
49
+ const headerStart = FRONTMATTER_DELIM.length + 1; // after first "---\n"
50
+ // Search for the closing fence. Accept either `\n---\n` (frontmatter
51
+ // followed by body) or `\n---` at the very end of the document
52
+ // (frontmatter with no trailing newline). Start the search at
53
+ // `headerStart - 1` so the empty-frontmatter case `---\n---\n...`
54
+ // is detected (the `\n---` at index 3 immediately follows the opener).
55
+ const searchFrom = headerStart - 1;
56
+ let closeIdx = -1;
57
+ let bodyStart = -1;
58
+ let i = searchFrom;
59
+ while (i <= text.length - `\n${FRONTMATTER_DELIM}`.length) {
60
+ const candidate = text.indexOf(`\n${FRONTMATTER_DELIM}`, i);
61
+ if (candidate === -1)
62
+ break;
63
+ const afterFence = candidate + `\n${FRONTMATTER_DELIM}`.length;
64
+ if (afterFence === text.length) {
65
+ // `\n---` at end-of-string — frontmatter ends here, body is empty.
66
+ closeIdx = candidate;
67
+ bodyStart = afterFence;
68
+ break;
69
+ }
70
+ if (text.charAt(afterFence) === "\n") {
71
+ // `\n---\n` — body starts after the trailing newline.
72
+ closeIdx = candidate;
73
+ bodyStart = afterFence + 1;
74
+ break;
75
+ }
76
+ // `\n---` followed by more `-` (e.g. `\n----`) — keep searching.
77
+ i = candidate + 1;
78
+ }
79
+ if (closeIdx === -1 || bodyStart === -1) {
80
+ // Malformed frontmatter (no closing delimiter): treat as plain body.
81
+ return { data: {}, body: text };
82
+ }
83
+ const header = text.slice(headerStart, closeIdx);
84
+ const body = text.slice(bodyStart);
85
+ return { data: parseFrontmatterHeader(header), body };
86
+ }
87
+ function parseFrontmatterHeader(header) {
88
+ const out = {};
89
+ const lines = header.split("\n");
90
+ let i = 0;
91
+ while (i < lines.length) {
92
+ const line = lines[i] ?? "";
93
+ if (line.trim() === "" || line.trimStart().startsWith("#")) {
94
+ i++;
95
+ continue;
96
+ }
97
+ // Top-level keys are unindented `key: value` or `key:` (then list).
98
+ const m = /^([A-Za-z_][\w-]*)\s*:\s*(.*)$/.exec(line);
99
+ if (!m) {
100
+ i++;
101
+ continue;
102
+ }
103
+ const key = m[1];
104
+ const inlineValue = (m[2] ?? "").trim();
105
+ if (inlineValue === "") {
106
+ // Possible block list.
107
+ const list = [];
108
+ let j = i + 1;
109
+ while (j < lines.length) {
110
+ const next = lines[j] ?? "";
111
+ const itemMatch = /^\s*-\s+(.*)$/.exec(next);
112
+ if (!itemMatch)
113
+ break;
114
+ list.push(unwrapScalar((itemMatch[1] ?? "").trim()));
115
+ j++;
116
+ }
117
+ if (list.length > 0) {
118
+ out[key] = list;
119
+ i = j;
120
+ continue;
121
+ }
122
+ // Empty value with no list — record empty string for completeness.
123
+ out[key] = "";
124
+ i++;
125
+ continue;
126
+ }
127
+ out[key] = unwrapScalar(inlineValue);
128
+ i++;
129
+ }
130
+ return out;
131
+ }
132
+ function unwrapScalar(value) {
133
+ if (value.length >= 2) {
134
+ const first = value.charAt(0);
135
+ const last = value.charAt(value.length - 1);
136
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
137
+ return value.slice(1, -1);
138
+ }
139
+ }
140
+ return value;
141
+ }
142
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { ANONYMOUS_COMPONENT_NAME, HYDRATE_MARKER_ATTR, Island, SKIP_SSR_MARKER_ATTR, resolveWhen, type IslandElement, type IslandProps, } from "./island.js";
2
+ export { scheduleHydrate, mountIslands, mountNewIslands, cancelPendingIslands, unmountIslands, } from "./runtime.js";
3
+ export type { IslandManifest, IslandManifestValue } from "./runtime.js";
4
+ export { DEFAULT_WHEN, isWhen, WHEN_VALUES, type When } from "./types.js";
5
+ export { definePlugin, type ZfbBuildHookContext, type ZfbDevMiddlewareContext, type ZfbDevMiddlewareHandler, type ZfbDevMiddlewareRequest, type ZfbDevMiddlewareResponse, type ZfbPlugin, type ZfbPluginLogger, } from "./plugins.js";
6
+ export { ContentBlockquote, ContentCode, ContentH2, ContentH3, ContentH4, ContentLink, ContentOl, ContentParagraph, ContentStrong, ContentTable, ContentUl, defaultComponents, type ContentComponentElement, type ContentComponentProps, } from "./content.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAW1E,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,SAAS,EACT,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC"}