@jk2908/mdsrc 0.2.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 - 2026-06-23
4
+
5
+ - Breaking: replaced the verbose schema API with a simplified string-based syntax. Fields are now declared as `fieldName: 'type'` instead of `fieldName: { type: 'type' }`, optional fields use a `?` suffix (e.g. `'metadata?'`), and nested objects are declared inline without a `schema` wrapper.
6
+ - Added LRU file cache with bounded size and promotion-on-read to skip redundant disk writes during rebuilds.
7
+ - Added structured error codes to validation issues (`INVALID_INPUT`, `UNKNOWN_KEY`, `MISSING_REQUIRED`, `INVALID_TYPE`, `INVALID_DATE`) for programmatic error handling.
8
+ - Added date coercion support for `Date` objects and numeric timestamps in addition to strings.
9
+ - Added unknown key detection during validation — fields not declared in the schema now produce an `UNKNOWN_KEY` issue.
10
+ - Added GitHub Actions CI workflow to run tests on push and pull requests.
11
+ - Moved tests to a standalone `test.ts` file with a dedicated vitest config.
12
+ - Updated the basic example and removed the components example.
13
+ - Breaking: replaced `markdown-it-ts` with `satteri` for Rust-powered markdown parsing. The plugin config now uses `compileOptions` (from satteri) instead of the previous `markdown.plugins` and `markdown.config` structure.
14
+
15
+ ## 0.3.0 - 2026-05-21
16
+
17
+ - Breaking: moved markdown customization under the `markdown` key, replacing the old root-level `plugins` option with `markdown.plugins`.
18
+ - Replaced the HTML renderer dependency with `markdown-it-ts` and added `markdown.config` support for renderer options.
19
+ - Re-exported `MarkdownItConfig` from `@jk2908/mdsrc` so consumers can type markdown renderer config without importing from `markdown-it-ts` directly.
20
+ - Updated the README examples to document the nested markdown config and the default renderer options merge.
21
+
3
22
  ## 0.2.0 - 2026-05-07
4
23
 
5
24
  - Breaking: replaced the generated `body` field with `html` and `markdown` on every entry.
package/README.md CHANGED
@@ -11,14 +11,21 @@ npm install @jk2908/mdsrc
11
11
  ## Usage
12
12
 
13
13
  ```ts
14
- import plugin from '@jk2908/mdsrc'
14
+ import plugin, { type MarkdownItConfig } from '@jk2908/mdsrc'
15
15
  import comark from '@comark/markdown-it'
16
16
  import { defineConfig } from 'vite'
17
17
 
18
+ const markdownItConfig: MarkdownItConfig = {
19
+ linkify: true,
20
+ }
21
+
18
22
  export default defineConfig({
19
23
  plugins: [
20
24
  plugin({
21
- plugins: [comark],
25
+ markdown: {
26
+ plugins: [comark],
27
+ config: markdownItConfig,
28
+ },
22
29
  collections: [
23
30
  {
24
31
  dir: 'content',
@@ -33,15 +40,16 @@ export default defineConfig({
33
40
  })
34
41
  ```
35
42
 
36
- The plugin reads markdown content, validates frontmatter against your schema, and generates typed modules during build and watch. Root config uses `collections`, optional `plugins`, and `logger`. Collection config uses `name`, `dir`, and `schema`.
43
+ The plugin reads markdown content, validates frontmatter against your schema, and generates typed modules during build and watch. Root config uses `collections`, optional `markdown`, and `logger`. Collection config uses `name`, `dir`, and `schema`.
37
44
 
38
45
  Each entry exports both `html` and `markdown`.
39
46
 
40
- - `html` is rendered with `markdown-it`, with hard line breaks preserved.
47
+ - `html` is rendered with `markdown-it-ts`, with hard line breaks preserved by default.
41
48
  - Raw HTML is escaped by default because the renderer runs with `html: false`.
42
49
  - `markdown` preserves the original body for custom renderers like `@comark/react`.
43
50
 
44
- Root `plugins` accepts markdown-it plugins directly or tuples like `[plugin, ...args]` and applies them to every collection.
51
+ Use `markdown.plugins` for `markdown-it-ts` compatible plugins, and `markdown.config` to pass renderer options.
52
+ `MarkdownItConfig` is re-exported from `@jk2908/mdsrc`, and `markdown.config` is merged with the default renderer options `{ html: false, breaks: true }`.
45
53
 
46
54
  See `examples/basic` for the default HTML output flow and `examples/components` for the shared `@comark/markdown-it` plugin used with React.
47
55
 
package/dist/config.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export declare const NAME = "mdsrc";
2
2
  export declare const PKG_NAME = "@jk2908/mdsrc";
3
3
  export declare const GENERATED_DIR = ".mdsrc";
4
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,UAAU,CAAA;AAC3B,eAAO,MAAM,QAAQ,kBAAoB,CAAA;AACzC,eAAO,MAAM,aAAa,WAAa,CAAA"}
package/dist/config.js ADDED
@@ -0,0 +1,4 @@
1
+ export const NAME = 'mdsrc';
2
+ export const PKG_NAME = `@jk2908/${NAME}`;
3
+ export const GENERATED_DIR = `.${NAME}`;
4
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG,OAAO,CAAA;AAC3B,MAAM,CAAC,MAAM,QAAQ,GAAG,WAAW,IAAI,EAAE,CAAA;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,IAAI,EAAE,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,21 +1,16 @@
1
1
  import type { Plugin } from 'vite';
2
2
  import type { BuildContext, PluginConfig } from './types.js';
3
+ export type { CompileOptions } from './types.js';
3
4
  /**
4
5
  * Read every markdown file in a collection and turn it into the raw entry shape
5
6
  * add mdsrc metadata like slug and filename alongside the trimmed body
6
7
  * return an empty list if the directory read fails
7
8
  */
8
- export declare function create(dir: string, buildContext: BuildContext): Promise<{
9
- __mdsrc: {
10
- slug: string;
11
- filename: string;
12
- };
13
- html: string;
14
- markdown: string;
15
- }[]>;
9
+ export declare function create(dir: string, buildContext: BuildContext): Promise<any[]>;
16
10
  /**
17
- * Build the vite plugin that validates collections and writes the generated modules
11
+ * Build the Vite plugin that validates collections and writes the generated modules
18
12
  * keep the runtime data and declaration files in the same pass
19
13
  * resolve package imports from the generated directory
20
14
  */
21
15
  export default function mdsrc(config: PluginConfig): Plugin;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;AAIjD,OAAO,KAAK,EACX,YAAY,EAIZ,YAAY,EAIZ,MAAM,YAAY,CAAA;AAanB,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAoBhD;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,kBAgDnE;AA4VD;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAoJ1D"}
package/dist/index.js CHANGED
@@ -1,8 +1,27 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
1
20
  // src/index.ts
2
21
  import { realpathSync } from "node:fs";
3
22
  import fs from "node:fs/promises";
4
23
  import path from "node:path";
5
- import MarkdownIt from "markdown-it";
24
+ import { markdownToHtml } from "satteri";
6
25
 
7
26
  // src/config.ts
8
27
  var NAME = "mdsrc";
@@ -113,42 +132,48 @@ function debounce(fn, wait) {
113
132
  }, wait);
114
133
  };
115
134
  }
135
+ function dedent(str) {
136
+ return str.replace(/^\n/, "").replace(/\s+$/, "").split(`
137
+ `).filter(Boolean).map((line) => line.replace(/^\s+/, "")).join(`
138
+ `);
139
+ }
140
+ function isRecord(value) {
141
+ return typeof value === "object" && value !== null && !Array.isArray(value);
142
+ }
143
+ function deep(obj, path, value) {
144
+ const parts = path.split(".");
145
+ let cur = obj;
146
+ for (let i = 0;i < parts.length - 1; i++) {
147
+ const k = parts[i];
148
+ cur[k] ??= {};
149
+ cur = cur[k];
150
+ }
151
+ cur[parts.at(-1)] = value;
152
+ }
116
153
 
117
154
  // src/index.ts
118
155
  var fileCache = new Map;
119
- function toModuleName(name) {
120
- return name.toLowerCase();
121
- }
122
- function parse(content) {
123
- const regex = /^---\r?\n([\s\S]*?)\r?\n---([\s\S]*)$/;
124
- const match = content.match(regex);
125
- const metadata = {};
126
- if (!match)
127
- throw new Error("Invalid frontmatter");
128
- const [, frontmatter, body] = match;
129
- if (frontmatter) {
130
- for (const line of frontmatter.split(`
131
- `)) {
132
- const [key, value] = line.split(": ").map((str) => str.trim());
133
- metadata[key] = value;
156
+ var DEFAULT_COMPILE_OPTIONS = {
157
+ features: {
158
+ frontmatter: true
159
+ }
160
+ };
161
+ async function parse(frontmatter) {
162
+ if (!frontmatter)
163
+ return {};
164
+ const { kind, value } = frontmatter;
165
+ switch (kind) {
166
+ case "yaml": {
167
+ return (await import("yaml")).parse(value);
168
+ }
169
+ case "toml": {
170
+ return (await import("smol-toml")).parse(value);
134
171
  }
135
172
  }
136
- return { metadata, body };
137
173
  }
138
174
  async function create(dir, buildContext) {
139
- const { logger: logger2, plugins = [] } = buildContext;
140
- const markdown = new MarkdownIt({
141
- html: false,
142
- breaks: true
143
- });
144
- for (const plugin of plugins) {
145
- if (typeof plugin === "function") {
146
- markdown.use(plugin);
147
- continue;
148
- }
149
- const [applyPlugin, ...params] = plugin;
150
- markdown.use(applyPlugin, ...params);
151
- }
175
+ const { logger: logger2, compileOptions = {} } = buildContext;
176
+ const { features, ...restCompileOptions } = compileOptions;
152
177
  try {
153
178
  const files = (await fs.readdir(dir)).filter((file) => path.extname(file) === ".md");
154
179
  const filePaths = files.map((file) => path.join(dir, file));
@@ -158,16 +183,21 @@ async function create(dir, buildContext) {
158
183
  }
159
184
  return Promise.all(filePaths.map(async (filePath) => {
160
185
  const file = path.basename(filePath);
161
- const parsed = parse(await fs.readFile(filePath, "utf-8"));
162
- const body = parsed.body ? parsed.body.trim() : "";
186
+ const { html, frontmatter: rawFrontmatter } = markdownToHtml(await fs.readFile(filePath, "utf-8"), {
187
+ features: {
188
+ ...DEFAULT_COMPILE_OPTIONS.features,
189
+ ...features
190
+ },
191
+ ...restCompileOptions
192
+ });
193
+ const frontmatter = await parse(rawFrontmatter);
163
194
  return {
164
- ...parsed.metadata,
195
+ ...frontmatter,
165
196
  __mdsrc: {
166
197
  slug: path.basename(file, ".md").toLowerCase().replace(/\s+/g, "-"),
167
198
  filename: file
168
199
  },
169
- html: body ? markdown.render(body).trim() : body,
170
- markdown: body
200
+ body: html.trim()
171
201
  };
172
202
  }));
173
203
  } catch (err) {
@@ -212,79 +242,103 @@ function validate(input, schema) {
212
242
  issues.push({ message: "Input must be an object" });
213
243
  return { issues };
214
244
  }
215
- for (const key in schema) {
216
- const entry = schema[key];
217
- if (!(key in input)) {
218
- if (!entry.optional) {
219
- issues.push({ message: `Missing required key: ${key}` });
245
+ function walk(key, schemaValue, data) {
246
+ const { optional, key: parsedKey } = parseKey(key);
247
+ if (data === undefined) {
248
+ if (!optional) {
249
+ issues.push({ message: `Missing required key: ${parsedKey}` });
220
250
  }
221
- continue;
251
+ return;
222
252
  }
223
- const value = input[key];
224
- switch (entry.type) {
225
- case "string": {
226
- if (typeof value !== "string") {
227
- issues.push({ message: `Key ${key} must be a string` });
228
- } else {
229
- if (entry.minLength && value.length < entry.minLength) {
230
- issues.push({
231
- message: `Key ${key} must be at least ${entry.minLength} characters`
232
- });
233
- }
234
- if (entry.maxLength && value.length > entry.maxLength) {
235
- issues.push({
236
- message: `Key ${key} must be at most ${entry.maxLength} characters`
237
- });
253
+ if (typeof schemaValue === "string") {
254
+ switch (schemaValue) {
255
+ case "string": {
256
+ if (typeof data !== "string") {
257
+ issues.push({ message: `Key ${parsedKey} must be a string` });
258
+ return;
238
259
  }
239
- validated[key] = value;
260
+ deep(validated, parsedKey, data);
261
+ break;
240
262
  }
241
- break;
242
- }
243
- case "number": {
244
- let num = value;
245
- if (typeof value === "string" && !Number.isNaN(Number(value))) {
246
- num = Number(value);
247
- }
248
- if (typeof num !== "number") {
249
- issues.push({ message: `Key ${key} must be a number` });
250
- } else {
251
- validated[key] = num;
252
- }
253
- break;
254
- }
255
- case "boolean": {
256
- let bool = value;
257
- if (typeof value === "string") {
258
- if (value.toLowerCase() === "true") {
259
- bool = true;
260
- } else if (value.toLowerCase() === "false") {
261
- bool = false;
263
+ case "number": {
264
+ let num = data;
265
+ if (typeof data === "string" && !Number.isNaN(Number(data))) {
266
+ num = Number(data);
267
+ }
268
+ if (typeof num !== "number" || Number.isNaN(num)) {
269
+ issues.push({ message: `Key ${parsedKey} must be a number` });
270
+ return;
262
271
  }
272
+ deep(validated, parsedKey, num);
273
+ break;
263
274
  }
264
- if (typeof bool !== "boolean") {
265
- issues.push({ message: `Key ${key} must be a boolean` });
266
- } else {
267
- validated[key] = bool;
275
+ case "boolean": {
276
+ let bool = data;
277
+ if (typeof data === "string") {
278
+ if (data.toLowerCase() === "true") {
279
+ bool = true;
280
+ } else if (data.toLowerCase() === "false") {
281
+ bool = false;
282
+ }
283
+ }
284
+ if (typeof bool !== "boolean") {
285
+ issues.push({ message: `Key ${parsedKey} must be a boolean` });
286
+ return;
287
+ }
288
+ deep(validated, parsedKey, bool);
289
+ break;
268
290
  }
269
- break;
270
- }
271
- case "date": {
272
- if (typeof value !== "string") {
273
- issues.push({ message: `Key ${key} must be a date` });
274
- } else {
275
- const date = new Date(value);
291
+ case "date": {
292
+ if (typeof data !== "string") {
293
+ issues.push({ message: `Key ${parsedKey} must be a date` });
294
+ return;
295
+ }
296
+ const date = new Date(data);
276
297
  if (Number.isNaN(date.getTime())) {
277
- issues.push({ message: `Key ${key} must be a valid date` });
278
- } else {
279
- validated[key] = date.toISOString();
298
+ issues.push({ message: `Key ${parsedKey} must be a valid date` });
299
+ return;
280
300
  }
301
+ deep(validated, parsedKey, date.toISOString());
302
+ break;
281
303
  }
282
- break;
304
+ }
305
+ } else {
306
+ if (!isRecord(data)) {
307
+ issues.push({ message: `Key ${parsedKey} must be an object` });
308
+ return;
309
+ }
310
+ const obj = data;
311
+ for (const subKey in schemaValue) {
312
+ walk(`${parsedKey}.${subKey}`, schemaValue[subKey], obj[parseKey(subKey).key]);
283
313
  }
284
314
  }
285
315
  }
316
+ for (const key in schema) {
317
+ walk(key, schema[key], input[parseKey(key).key]);
318
+ }
286
319
  return issues.length ? { issues } : { value: validated };
287
320
  }
321
+ function parseKey(k) {
322
+ const optional = k.endsWith("?");
323
+ return {
324
+ optional,
325
+ key: optional ? k.slice(0, -1) : k
326
+ };
327
+ }
328
+ function schemaValueToType(schema) {
329
+ const fields = Object.entries(schema).map(([k, v]) => {
330
+ const { key, optional } = parseKey(k);
331
+ let type;
332
+ if (typeof v === "string") {
333
+ type = v === "date" ? "string" : v;
334
+ } else {
335
+ type = schemaValueToType(v);
336
+ }
337
+ return `${key}${optional ? "?" : ""}: ${type}`;
338
+ }).join(`
339
+ `);
340
+ return `{ ${fields} }`;
341
+ }
288
342
  async function build(src, buildContext) {
289
343
  const { logger: logger2, outDir } = buildContext;
290
344
  let names = [];
@@ -297,14 +351,13 @@ async function build(src, buildContext) {
297
351
  const raw = await create(path.join(process.cwd(), collection.dir), buildContext);
298
352
  const validated = await Promise.all(raw.map(async (item) => {
299
353
  try {
300
- const { html, markdown, __mdsrc, ...metadata } = item;
354
+ const { body, __mdsrc, ...metadata } = item;
301
355
  const res = validate(metadata, collection.schema);
302
356
  if (res.issues)
303
357
  throw new Error(JSON.stringify(res.issues, null, 2));
304
358
  return {
305
- html,
306
- markdown,
307
359
  ...res.value,
360
+ body,
308
361
  __mdsrc
309
362
  };
310
363
  } catch (err) {
@@ -321,11 +374,8 @@ async function build(src, buildContext) {
321
374
  const promises = [];
322
375
  promises.push(maybeWrite(path.join(outDir, "types.ts"), `
323
376
  ${names.map((name) => `
324
- export type ${capitalise(name)} = {
325
- html: string
326
- markdown: string
327
- ${Object.entries(collections[name].schema).map(([key, entry]) => `${key}${entry.optional ? "?" : ""}: ${entry.type === "date" ? "string" : entry.type}`).join(`
328
- `)}
377
+ export type ${capitalise(name)} = ${schemaValueToType(collections[name].schema)} & {
378
+ body: string,
329
379
  __mdsrc: {
330
380
  slug: string
331
381
  filename: string
@@ -366,7 +416,9 @@ async function build(src, buildContext) {
366
416
  throw err;
367
417
  }
368
418
  }
369
- var normaliseWatchPath = (p) => p.replace(/\\/g, "/");
419
+ function normaliseWatchPath(p) {
420
+ return p.replace(/\\/g, "/");
421
+ }
370
422
  function mdsrc(config) {
371
423
  const src = config.collections;
372
424
  const logger2 = new Logger(config.logger?.level ?? "debug");
@@ -383,10 +435,12 @@ function mdsrc(config) {
383
435
  return normaliseWatchPath(absolutePath);
384
436
  }
385
437
  };
386
- const isWatchedFile = (filePath) => watchedRoots.some((root) => resolveWatchFile(filePath).startsWith(root));
438
+ function watchedFile(filePath) {
439
+ return watchedRoots.some((root) => resolveWatchFile(filePath).startsWith(root));
440
+ }
387
441
  const buildContext = {
388
442
  logger: logger2,
389
- plugins: config.plugins,
443
+ compileOptions: config.compileOptions,
390
444
  outDir,
391
445
  names: []
392
446
  };
@@ -394,7 +448,7 @@ function mdsrc(config) {
394
448
  let rebuildQueued = false;
395
449
  let rebuildReason = "change";
396
450
  const rebuild = debounce((event, filePath) => {
397
- const queue = () => {
451
+ function queue() {
398
452
  (async () => {
399
453
  if (rebuildRunning) {
400
454
  rebuildQueued = true;
@@ -413,8 +467,8 @@ function mdsrc(config) {
413
467
  } while (rebuildQueued);
414
468
  rebuildRunning = false;
415
469
  })();
416
- };
417
- if (!isWatchedFile(filePath))
470
+ }
471
+ if (!watchedFile(filePath))
418
472
  return;
419
473
  const file = resolveWatchFile(filePath);
420
474
  rebuildReason = `${event}: ${path.relative(watchRoot, file)}`;
@@ -466,9 +520,45 @@ function mdsrc(config) {
466
520
  }
467
521
  };
468
522
  }
523
+ function toModuleName(name) {
524
+ return name.toLowerCase();
525
+ }
526
+ if (import.meta.vitest) {
527
+ const { it, expect, describe } = import.meta.vitest;
528
+ const now = Date.now();
529
+ const yaml = {
530
+ kind: "yaml",
531
+ value: dedent(`
532
+ title: mdsrc
533
+ date: ${now}
534
+ `)
535
+ };
536
+ const toml = {
537
+ kind: "toml",
538
+ value: dedent(`
539
+ title = "mdsrc"
540
+ date = ${now}
541
+ `)
542
+ };
543
+ describe("markdown parsing", () => {
544
+ it("parses frontmatter", async () => {
545
+ for (const f of [yaml, toml]) {
546
+ const frontmatter = await parse(f);
547
+ expect(frontmatter).toEqual({
548
+ title: "mdsrc",
549
+ date: now
550
+ });
551
+ }
552
+ });
553
+ it("returns empty object for missing frontmatter", async () => {
554
+ const frontmatter = await parse(null);
555
+ expect(frontmatter).toEqual({});
556
+ });
557
+ });
558
+ }
469
559
  export {
470
560
  mdsrc as default,
471
561
  create
472
562
  };
473
563
 
474
- //# debugId=4A1919CF4AE2D2C864756E2164756E21
564
+ //# debugId=0F1D108961D2552964756E2164756E21
package/dist/index.js.map CHANGED
@@ -2,12 +2,12 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/config.ts", "../src/logger.ts", "../src/utils.ts"],
4
4
  "sourcesContent": [
5
- "import { realpathSync } from 'node:fs'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport MarkdownIt from 'markdown-it'\n\nimport type {\n\tBuildContext,\n\tCollection,\n\tEntries,\n\tIssue,\n\tPluginConfig,\n\tRaw,\n\tResult,\n\tSchema,\n} from './types.js'\nimport { GENERATED_DIR, PKG_NAME } from './config.js'\nimport { Logger } from './logger.js'\nimport { capitalise, debounce, pluralise } from './utils.js'\n\nconst fileCache = new Map<string, string>()\n\nfunction toModuleName(name: string) {\n\treturn name.toLowerCase()\n}\n\n/**\n * Split a markdown file into frontmatter data and the body content\n * keep the format small so the parser stays easy to trust\n * fail fast if the opening fence is missing\n */\nfunction parse(content: string) {\n\t// look for one fenced frontmatter block right at the top\n\t// leave the rest of the markdown body alone\n\tconst regex = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---([\\s\\S]*)$/\n\tconst match = content.match(regex)\n\tconst metadata: Entries = {}\n\n\tif (!match) throw new Error('Invalid frontmatter')\n\n\tconst [, frontmatter, body] = match\n\n\tif (frontmatter) {\n\t\t// treat each line as a simple key: value pair\n\t\t// this is a small subset, not full yaml\n\t\tfor (const line of frontmatter.split('\\n')) {\n\t\t\tconst [key, value] = line.split(': ').map(str => str.trim())\n\t\t\tmetadata[key as keyof Entries] = value\n\t\t}\n\t}\n\n\treturn { metadata, body }\n}\n\n/**\n * Read every markdown file in a collection and turn it into the raw entry shape\n * add mdsrc metadata like slug and filename alongside the trimmed body\n * return an empty list if the directory read fails\n */\nexport async function create(dir: string, buildContext: BuildContext) {\n\tconst { logger, plugins = [] } = buildContext\n\n\tconst markdown = new MarkdownIt({\n\t\thtml: false,\n\t\tbreaks: true,\n\t})\n\n\tfor (const plugin of plugins) {\n\t\tif (typeof plugin === 'function') {\n\t\t\tmarkdown.use(plugin)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst [applyPlugin, ...params] = plugin\n\t\tmarkdown.use(applyPlugin, ...params)\n\t}\n\n\ttry {\n\t\t// only pick up markdown files from this directory\n\t\t// leave everything else alone\n\t\tconst files = (await fs.readdir(dir)).filter(\n\t\t\t(file: string) => path.extname(file) === '.md',\n\t\t)\n\t\tconst filePaths = files.map(file => path.join(dir, file))\n\n\t\tif (!files.length) {\n\t\t\tconsole.warn(`mdsrc: ${dir} is empty`)\n\t\t\treturn []\n\t\t}\n\n\t\treturn Promise.all(\n\t\t\tfilePaths.map(async filePath => {\n\t\t\t\tconst file = path.basename(filePath)\n\n\t\t\t\t// keep the parsed fields, body, and mdsrc metadata together\n\t\t\t\t// build the slug from the filename\n\t\t\t\tconst parsed = parse(await fs.readFile(filePath, 'utf-8'))\n\n\t\t\t\tconst body = parsed.body ? parsed.body.trim() : ''\n\n\t\t\t\treturn {\n\t\t\t\t\t...parsed.metadata,\n\t\t\t\t\t__mdsrc: {\n\t\t\t\t\t\tslug: path.basename(file, '.md').toLowerCase().replace(/\\s+/g, '-'),\n\t\t\t\t\t\tfilename: file,\n\t\t\t\t\t},\n\t\t\t\t\thtml: body ? markdown.render(body).trim() : body,\n\t\t\t\t\tmarkdown: body,\n\t\t\t\t} satisfies Raw\n\t\t\t}),\n\t\t)\n\t} catch (err) {\n\t\tlogger.error('[create]: failed to create entries', err)\n\t\treturn []\n\t}\n}\n\n/**\n * Write a file only if the content has changed since the last build\n */\nasync function maybeWrite(filePath: string, content: string) {\n\tconst cached = fileCache.get(filePath)\n\n\tif (cached === content) {\n\t\ttry {\n\t\t\tawait fs.access(filePath)\n\t\t\treturn false\n\t\t} catch (err) {\n\t\t\tif (!(err instanceof Error) || !('code' in err) || err.code !== 'ENOENT') {\n\t\t\t\tthrow err\n\t\t\t}\n\n\t\t\t// file was deleted since the last build, fall through and write it again\n\t\t}\n\t}\n\n\tif (cached === undefined) {\n\t\ttry {\n\t\t\tconst current = await fs.readFile(filePath, 'utf-8')\n\t\t\tfileCache.set(filePath, current)\n\n\t\t\tif (current === content) {\n\t\t\t\tfileCache.set(filePath, content)\n\t\t\t\treturn false\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (!(err instanceof Error) || !('code' in err) || err.code !== 'ENOENT') {\n\t\t\t\tthrow err\n\t\t\t}\n\t\t}\n\t}\n\n\t// file is new or changed since the last build, write it and refresh the cache\n\tawait fs.writeFile(filePath, content)\n\tfileCache.set(filePath, content)\n\n\treturn true\n}\n\n/**\n * Check one entry against the declared schema and coerce what can be coerced\n * leave missing optional keys alone instead of treating them as errors\n * normalise dates to iso strings for output\n */\nfunction validate(input: Entries, schema: Schema) {\n\t// keep valid values separate so bad fields never sneak into output\n\tconst validated: Entries = {}\n\t// collect every problem so one pass can report the lot\n\tconst issues: Issue[] = []\n\n\t// bail out early if the frontmatter is not even an object\n\tif (typeof input !== 'object' || input === null) {\n\t\tissues.push({ message: 'Input must be an object' })\n\t\treturn { issues } satisfies Result<Entries>\n\t}\n\n\t// drive validation from the schema so the rules always stay in charge\n\tfor (const key in schema) {\n\t\tconst entry = schema[key]\n\n\t\t// if the key is missing, only complain when the schema says it must exist\n\t\tif (!(key in input)) {\n\t\t\tif (!entry.optional) {\n\t\t\t\tissues.push({ message: `Missing required key: ${key}` })\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\t// once the key exists, coerce it into the shape the schema expects\n\t\tconst value = input[key]\n\n\t\tswitch (entry.type) {\n\t\t\tcase 'string': {\n\t\t\t\t// strings just need the basic type check and any length limits\n\t\t\t\tif (typeof value !== 'string') {\n\t\t\t\t\tissues.push({ message: `Key ${key} must be a string` })\n\t\t\t\t} else {\n\t\t\t\t\tif (entry.minLength && value.length < entry.minLength) {\n\t\t\t\t\t\tissues.push({\n\t\t\t\t\t\t\tmessage: `Key ${key} must be at least ${entry.minLength} characters`,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\n\t\t\t\t\tif (entry.maxLength && value.length > entry.maxLength) {\n\t\t\t\t\t\tissues.push({\n\t\t\t\t\t\t\tmessage: `Key ${key} must be at most ${entry.maxLength} characters`,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\n\t\t\t\t\tvalidated[key] = value\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'number': {\n\t\t\t\tlet num = value\n\n\t\t\t\t// frontmatter usually starts life as text, so numeric strings still count\n\t\t\t\tif (typeof value === 'string' && !Number.isNaN(Number(value))) {\n\t\t\t\t\tnum = Number(value)\n\t\t\t\t}\n\n\t\t\t\tif (typeof num !== 'number') {\n\t\t\t\t\tissues.push({ message: `Key ${key} must be a number` })\n\t\t\t\t} else {\n\t\t\t\t\tvalidated[key] = num\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'boolean': {\n\t\t\t\tlet bool = value\n\t\t\t\t// booleans often come through as the words true or false\n\t\t\t\tif (typeof value === 'string') {\n\t\t\t\t\tif (value.toLowerCase() === 'true') {\n\t\t\t\t\t\tbool = true\n\t\t\t\t\t} else if (value.toLowerCase() === 'false') {\n\t\t\t\t\t\tbool = false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (typeof bool !== 'boolean') {\n\t\t\t\t\tissues.push({ message: `Key ${key} must be a boolean` })\n\t\t\t\t} else {\n\t\t\t\t\tvalidated[key] = bool\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'date': {\n\t\t\t\t// keep dates as iso strings because that is what generated output exposes\n\t\t\t\tif (typeof value !== 'string') {\n\t\t\t\t\tissues.push({ message: `Key ${key} must be a date` })\n\t\t\t\t} else {\n\t\t\t\t\tconst date = new Date(value)\n\n\t\t\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\t\t\tissues.push({ message: `Key ${key} must be a valid date` })\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvalidated[key] = date.toISOString()\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// hand back the clean entry when validation passes\n\t// otherwise return the full issue list\n\treturn (issues.length ? { issues } : { value: validated }) satisfies Result<Entries>\n}\n\nasync function build(src: Collection[], buildContext: BuildContext) {\n\tconst { logger, outDir } = buildContext\n\tlet names: string[] = []\n\n\t// keep each validated collection beside its schema so emit stays in sync\n\tconst collections: Record<\n\t\tstring,\n\t\t{\n\t\t\titems: Raw[]\n\t\t\tschema: Schema\n\t\t}\n\t> = {}\n\n\ttry {\n\t\tif (!outDir) throw new Error('Output directory is not defined')\n\n\t\t// make sure the output directory exists before the writes begin\n\t\t// that way the emit step can stay simple\n\t\tawait fs.mkdir(outDir, { recursive: true })\n\n\t\t// read and validate every collection before writing anything out\n\t\t// this keeps the js and dts outputs in step\n\t\tfor (const collection of src) {\n\t\t\tconst raw = await create(path.join(process.cwd(), collection.dir), buildContext)\n\n\t\t\t// check each raw item before it makes it into the generated collection\n\t\t\t// bad entries get logged and dropped\n\t\t\tconst validated = await Promise.all(\n\t\t\t\traw.map(async item => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst { html, markdown, __mdsrc, ...metadata } = item\n\t\t\t\t\t\tconst res = validate(metadata, collection.schema)\n\n\t\t\t\t\t\tif (res.issues) throw new Error(JSON.stringify(res.issues, null, 2))\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\thtml,\n\t\t\t\t\t\t\tmarkdown,\n\t\t\t\t\t\t\t...res.value,\n\t\t\t\t\t\t\t__mdsrc,\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t\t`[buildStart]: failed to validate item in ${collection.name}`,\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn null\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tcollections[collection.name] = {\n\t\t\t\t// keep the cleaned items with the schema they came from\n\t\t\t\t// both js and dts generation read from this shape\n\t\t\t\titems: validated.filter(e => e !== null),\n\t\t\t\tschema: collection.schema,\n\t\t\t}\n\t\t}\n\n\t\t// take the collection names after validation has settled\n\t\t// every generated file then works from the same list\n\t\tnames = Object.keys(collections)\n\t\t// queue the file writes first so the emit phase can run together\n\t\t// wait for them once every output is ready\n\t\tconst promises = []\n\n\t\t// build the type file from the schema rather than the observed data\n\t\t// that keeps optional fields and date output honest\n\t\tpromises.push(\n\t\t\tmaybeWrite(\n\t\t\t\tpath.join(outDir, 'types.ts'),\n\t\t\t\t`\n\t\t\t\t\t${names\n\t\t\t\t\t\t// make one named type per collection so the dts mirrors the js surface.\n\t\t\t\t\t\t// html and markdown are always present on generated entries\n\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\tname => `\n\t\t\t\t\t\t\t\texport type ${capitalise(name)} = {\n\t\t\t\t\t\t\t\t\thtml: string\n\t\t\t\t\t\t\t\t\tmarkdown: string\n\t\t\t\t\t\t\t\t\t${Object.entries(collections[name].schema)\n\t\t\t\t\t\t\t\t\t\t// turn each schema field into a ts property line\n\t\t\t\t\t\t\t\t\t\t// keep optional markers and date strings in step with validation\n\t\t\t\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t\t\t\t([key, entry]) =>\n\t\t\t\t\t\t\t\t\t\t\t\t`${key}${entry.optional ? '?' : ''}: ${entry.type === 'date' ? 'string' : entry.type}`,\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t.join('\\n ')}\n\t\t\t\t\t\t\t\t\t__mdsrc: {\n\t\t\t\t\t\t\t\t\t\tslug: string\n\t\t\t\t\t\t\t\t\t\tfilename: string\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.join('\\n\\n')}`.trim(),\n\t\t\t),\n\t\t)\n\n\t\t// write the package surface separately so consumers get typed named exports\n\t\t// this mirrors the generated js entry file\n\t\tpromises.push(\n\t\t\tmaybeWrite(\n\t\t\t\tpath.join(outDir, 'index.d.ts'),\n\t\t\t\t`\t\n\t\t\t\t\timport type { ${names.map(name => capitalise(name)).join(', ')} } from './types.js'\n\n\t\t\t\t\t${names\n\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\tname => `\n\t\t\t\t\t\t\t\texport const all${capitalise(pluralise(name, 2))}: ${capitalise(name)}[]\n\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.join('\\n\\n')}\n\n\t\t\t\t\tdeclare module '${PKG_NAME}' {\n\t\t\t\t\t\t${names\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\tname => `\n\t\t\t\t\t\t\t\t\texport const all${capitalise(pluralise(name, 2))}: ${capitalise(name)}[]\n\t\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join('\\n\\n')}\n\t\t\t\t\t}\n\t\t\t\t`.trim(),\n\t\t\t),\n\t\t)\n\n\t\t// serialise each validated collection as a plain module for vite to load\n\t\t// empty collections still export a stable array shape\n\t\tfor (const name of names) {\n\t\t\tconst collection = collections[name]?.items\n\t\t\tconst fileName = toModuleName(name)\n\n\t\t\tpromises.push(\n\t\t\t\tmaybeWrite(\n\t\t\t\t\tpath.join(outDir, `${fileName}.js`),\n\t\t\t\t\t`export const all${capitalise(pluralise(name, 2))} = ${collection?.length ? JSON.stringify(collection) : '[]'}`.trim(),\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\n\t\t// stitch the per-collection modules into the public js entrypoint\n\t\t// this is the file the root package import resolves to\n\t\tpromises.push(\n\t\t\tmaybeWrite(\n\t\t\t\tpath.join(outDir, 'index.js'),\n\t\t\t\tnames.map(name => `export * from './${toModuleName(name)}.js'`).join('\\n'),\n\t\t\t),\n\t\t)\n\n\t\t// flush every generated artifact once all the content is ready\n\t\t// let any failed write fail the build\n\t\tconst writes = await Promise.all(promises)\n\t\tbuildContext.names = names\n\n\t\treturn writes.some(changed => changed)\n\t} catch (err) {\n\t\tlogger.error('[build]: failed to generate data', err)\n\t\tthrow err\n\t}\n}\n\n// convert watcher paths to a consistent slash format before comparing them\nconst normaliseWatchPath = (p: string) => p.replace(/\\\\/g, '/')\n\n/**\n * Build the vite plugin that validates collections and writes the generated modules\n * keep the runtime data and declaration files in the same pass\n * resolve package imports from the generated directory\n */\nexport default function mdsrc(config: PluginConfig): Plugin {\n\tconst src = config.collections\n\n\t// use one logger for the whole build so every step reports the same way\n\t// stay chatty outside production\n\tconst logger = new Logger(\n\t\tconfig.logger?.level ?? (process.env.NODE_ENV === 'production' ? 'error' : 'debug'),\n\t)\n\n\t// write generated files into a hidden folder at the project root\n\t// keep the generated surface out of src\n\tconst outDir = path.join(process.cwd(), GENERATED_DIR)\n\tconst watchRoot = normaliseWatchPath(realpathSync.native(process.cwd()))\n\tconst watchedRoots = src.map(c => `${normaliseWatchPath(path.join(watchRoot, c.dir))}/`)\n\n\t// watcher events can arrive through symlinked paths like /var while cwd has\n\t// already resolved to /private/var, so canonicalise the parent dir once and\n\t// reattach the file name for stable prefix checks\n\tconst resolveWatchFile = (filePath: string) => {\n\t\tconst absolutePath = path.resolve(watchRoot, filePath)\n\t\tconst parentPath = path.dirname(absolutePath)\n\n\t\ttry {\n\t\t\tconst resolvedParentPath = normaliseWatchPath(realpathSync.native(parentPath))\n\t\t\treturn normaliseWatchPath(\n\t\t\t\tpath.join(resolvedParentPath, path.basename(absolutePath)),\n\t\t\t)\n\t\t} catch {\n\t\t\treturn normaliseWatchPath(absolutePath)\n\t\t}\n\t}\n\n\tconst isWatchedFile = (filePath: string) =>\n\t\twatchedRoots.some(root => resolveWatchFile(filePath).startsWith(root))\n\n\t// pass shared build tools into helpers without dragging lots of state around\n\t// keep the helper signatures small\n\tconst buildContext = {\n\t\tlogger,\n\t\tplugins: config.plugins,\n\t\toutDir,\n\t\tnames: [],\n\t} satisfies BuildContext\n\n\tlet rebuildRunning = false\n\tlet rebuildQueued = false\n\tlet rebuildReason = 'change'\n\tconst rebuild = debounce((event: string, filePath: string) => {\n\t\tconst queue = () => {\n\t\t\tvoid (async () => {\n\t\t\t\t// collapse bursts of file events into one active rebuild plus a single\n\t\t\t\t// queued rerun when changes land mid-build\n\t\t\t\tif (rebuildRunning) {\n\t\t\t\t\trebuildQueued = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\trebuildRunning = true\n\n\t\t\t\tdo {\n\t\t\t\t\trebuildQueued = false\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst changed = await build(src, buildContext)\n\n\t\t\t\t\t\tif (changed) logger.info(`[watch]: content rebuilt (${rebuildReason})`)\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlogger.error('[watch] content rebuild failed', err)\n\t\t\t\t\t}\n\t\t\t\t} while (rebuildQueued)\n\n\t\t\t\trebuildRunning = false\n\t\t\t})()\n\t\t}\n\n\t\t// ignore anything outside the watched content dirs\n\t\tif (!isWatchedFile(filePath)) return\n\n\t\tconst file = resolveWatchFile(filePath)\n\n\t\trebuildReason = `${event}: ${path.relative(watchRoot, file)}`\n\t\tqueue()\n\t}, 75)\n\n\treturn {\n\t\tname: 'mdsrc',\n\t\tenforce: 'pre',\n\t\tconfig(viteConfig) {\n\t\t\tviteConfig.optimizeDeps ??= {}\n\t\t\tviteConfig.optimizeDeps.exclude = [\n\t\t\t\t...new Set([...(viteConfig.optimizeDeps.exclude ?? []), PKG_NAME]),\n\t\t\t]\n\n\t\t\tviteConfig.resolve ??= {}\n\n\t\t\tif (Array.isArray(viteConfig.resolve.alias)) {\n\t\t\t\tviteConfig.resolve.alias = [\n\t\t\t\t\t...viteConfig.resolve.alias,\n\t\t\t\t\t{\n\t\t\t\t\t\tfind: PKG_NAME,\n\t\t\t\t\t\treplacement: path.join(outDir, 'index.js'),\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t} else {\n\t\t\t\tviteConfig.resolve.alias = {\n\t\t\t\t\t...viteConfig.resolve.alias,\n\t\t\t\t\t[PKG_NAME]: path.join(outDir, 'index.js'),\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tasync buildStart() {\n\t\t\tawait build(src, buildContext)\n\t\t},\n\t\tconfigureServer(server: ViteDevServer) {\n\t\t\tlogger.info(\n\t\t\t\t`[configureServer]: Watching for changes in ./${src.map(c => c.dir).join(', ')}...`,\n\t\t\t)\n\n\t\t\tserver.watcher\n\t\t\t\t.on('add', (p: string) => rebuild('add', p))\n\t\t\t\t.on('change', (p: string) => rebuild('change', p))\n\t\t\t\t.on('unlink', (p: string) => rebuild('unlink', p))\n\t\t},\n\t\tresolveId(id) {\n\t\t\t// point imports at generated files rather than source files\n\t\t\t// that makes the package behave like a normal module\n\t\t\tif (id === PKG_NAME) {\n\t\t\t\treturn path.join(outDir, 'index.js')\n\t\t\t}\n\n\t\t\tif (id.startsWith(`${PKG_NAME}/`)) {\n\t\t\t\t// allow collection subpath imports once the build knows their names\n\t\t\t\t// leave unknown subpaths unresolved\n\t\t\t\tconst subpath = id.slice(PKG_NAME.length + 1)\n\t\t\t\tconst match = buildContext.names.find(\n\t\t\t\t\tname => name === subpath || toModuleName(name) === subpath,\n\t\t\t\t)\n\n\t\t\t\tif (match) {\n\t\t\t\t\treturn path.join(outDir, `${toModuleName(match)}.js`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null\n\t\t},\n\t}\n}\n",
5
+ "import { realpathSync } from 'node:fs'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { markdownToHtml, type CompileOptions, type MarkdownToHtmlResult } from 'satteri'\n\nimport type {\n\tBuildContext,\n\tCollection,\n\tEntries,\n\tIssue,\n\tPluginConfig,\n\tRaw,\n\tResult,\n\tSchema,\n} from './types.js'\nimport { GENERATED_DIR, PKG_NAME } from './config.js'\nimport { Logger } from './logger.js'\nimport { capitalise, debounce, dedent, deep, isRecord, pluralise } from './utils.js'\n\nconst fileCache = new Map<string, string>()\n\nconst DEFAULT_COMPILE_OPTIONS = {\n\tfeatures: {\n\t\tfrontmatter: true,\n\t},\n} satisfies CompileOptions\n\nexport type { CompileOptions } from './types.js'\n\n/**\n * Parse YAML or TOML frontmatter\n */\nasync function parse(frontmatter: MarkdownToHtmlResult['frontmatter']) {\n\tif (!frontmatter) return {}\n\n\tconst { kind, value } = frontmatter\n\n\tswitch (kind) {\n\t\tcase 'yaml': {\n\t\t\treturn (await import('yaml')).parse(value)\n\t\t}\n\t\tcase 'toml': {\n\t\t\treturn (await import('smol-toml')).parse(value)\n\t\t}\n\t}\n}\n\n/**\n * Read every markdown file in a collection and turn it into the raw entry shape\n * add mdsrc metadata like slug and filename alongside the trimmed body\n * return an empty list if the directory read fails\n */\nexport async function create(dir: string, buildContext: BuildContext) {\n\tconst { logger, compileOptions = {} } = buildContext\n\tconst { features, ...restCompileOptions } = compileOptions\n\n\ttry {\n\t\t// only pick up markdown files from this directory\n\t\t// leave everything else alone\n\t\tconst files = (await fs.readdir(dir)).filter(\n\t\t\t(file: string) => path.extname(file) === '.md',\n\t\t)\n\t\tconst filePaths = files.map(file => path.join(dir, file))\n\n\t\tif (!files.length) {\n\t\t\tconsole.warn(`mdsrc: ${dir} is empty`)\n\t\t\treturn []\n\t\t}\n\n\t\treturn Promise.all(\n\t\t\tfilePaths.map(async filePath => {\n\t\t\t\tconst file = path.basename(filePath)\n\n\t\t\t\tconst { html, frontmatter: rawFrontmatter } = markdownToHtml(\n\t\t\t\t\tawait fs.readFile(filePath, 'utf-8'),\n\t\t\t\t\t{\n\t\t\t\t\t\tfeatures: {\n\t\t\t\t\t\t\t...DEFAULT_COMPILE_OPTIONS.features,\n\t\t\t\t\t\t\t...features,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...restCompileOptions,\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\tconst frontmatter = await parse(rawFrontmatter)\n\n\t\t\t\treturn {\n\t\t\t\t\t...frontmatter,\n\t\t\t\t\t__mdsrc: {\n\t\t\t\t\t\tslug: path.basename(file, '.md').toLowerCase().replace(/\\s+/g, '-'),\n\t\t\t\t\t\tfilename: file,\n\t\t\t\t\t},\n\t\t\t\t\tbody: html.trim(),\n\t\t\t\t} satisfies Raw\n\t\t\t}),\n\t\t)\n\t} catch (err) {\n\t\tlogger.error('[create]: failed to create entries', err)\n\t\treturn []\n\t}\n}\n\n/**\n * Write a file only if the content has changed since the last build\n */\nasync function maybeWrite(filePath: string, content: string) {\n\tconst cached = fileCache.get(filePath)\n\n\tif (cached === content) {\n\t\ttry {\n\t\t\tawait fs.access(filePath)\n\t\t\treturn false\n\t\t} catch (err) {\n\t\t\tif (!(err instanceof Error) || !('code' in err) || err.code !== 'ENOENT') {\n\t\t\t\tthrow err\n\t\t\t}\n\n\t\t\t// file was deleted since the last build, fall through and write it again\n\t\t}\n\t}\n\n\tif (cached === undefined) {\n\t\ttry {\n\t\t\tconst current = await fs.readFile(filePath, 'utf-8')\n\t\t\tfileCache.set(filePath, current)\n\n\t\t\tif (current === content) {\n\t\t\t\tfileCache.set(filePath, content)\n\t\t\t\treturn false\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (!(err instanceof Error) || !('code' in err) || err.code !== 'ENOENT') {\n\t\t\t\tthrow err\n\t\t\t}\n\t\t}\n\t}\n\n\t// file is new or changed since the last build, write it and refresh the cache\n\tawait fs.writeFile(filePath, content)\n\tfileCache.set(filePath, content)\n\n\treturn true\n}\n\n/**\n * Check one entry against the declared schema and coerce what can be coerced\n * leave missing optional keys alone instead of treating them as errors\n * normalise dates to iso strings for output\n */\nfunction validate(input: Entries, schema: Schema) {\n\t// keep valid values separate so bad fields never sneak into output\n\tconst validated: Entries = {}\n\t// collect every problem so one pass can report the lot\n\tconst issues: Issue[] = []\n\n\t// bail out early if the frontmatter is not even an object\n\tif (typeof input !== 'object' || input === null) {\n\t\tissues.push({ message: 'Input must be an object' })\n\t\treturn { issues } satisfies Result<Entries>\n\t}\n\n\tfunction walk(key: string, schemaValue: Schema.Value, data: unknown) {\n\t\tconst { optional, key: parsedKey } = parseKey(key)\n\n\t\tif (data === undefined) {\n\t\t\tif (!optional) {\n\t\t\t\tissues.push({ message: `Missing required key: ${parsedKey}` })\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\t// check primitives\n\t\tif (typeof schemaValue === 'string') {\n\t\t\tswitch (schemaValue) {\n\t\t\t\tcase 'string': {\n\t\t\t\t\tif (typeof data !== 'string') {\n\t\t\t\t\t\tissues.push({ message: `Key ${parsedKey} must be a string` })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tdeep(validated, parsedKey, data)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tcase 'number': {\n\t\t\t\t\tlet num = data\n\n\t\t\t\t\tif (typeof data === 'string' && !Number.isNaN(Number(data))) {\n\t\t\t\t\t\tnum = Number(data)\n\t\t\t\t\t}\n\n\t\t\t\t\tif (typeof num !== 'number' || Number.isNaN(num)) {\n\t\t\t\t\t\tissues.push({ message: `Key ${parsedKey} must be a number` })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tdeep(validated, parsedKey, num)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tcase 'boolean': {\n\t\t\t\t\tlet bool = data\n\n\t\t\t\t\tif (typeof data === 'string') {\n\t\t\t\t\t\tif (data.toLowerCase() === 'true') {\n\t\t\t\t\t\t\tbool = true\n\t\t\t\t\t\t} else if (data.toLowerCase() === 'false') {\n\t\t\t\t\t\t\tbool = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (typeof bool !== 'boolean') {\n\t\t\t\t\t\tissues.push({ message: `Key ${parsedKey} must be a boolean` })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tdeep(validated, parsedKey, bool)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tcase 'date': {\n\t\t\t\t\tif (typeof data !== 'string') {\n\t\t\t\t\t\tissues.push({ message: `Key ${parsedKey} must be a date` })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tconst date = new Date(data)\n\n\t\t\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\t\t\tissues.push({ message: `Key ${parsedKey} must be a valid date` })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tdeep(validated, parsedKey, date.toISOString())\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif (!isRecord(data)) {\n\t\t\t\tissues.push({ message: `Key ${parsedKey} must be an object` })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst obj = data\n\n\t\t\tfor (const subKey in schemaValue) {\n\t\t\t\twalk(`${parsedKey}.${subKey}`, schemaValue[subKey], obj[parseKey(subKey).key])\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const key in schema) {\n\t\twalk(key, schema[key], input[parseKey(key).key])\n\t}\n\n\t// return the clean entry when validation passes, otherwise return the full\n\t// issue list\n\treturn (issues.length ? { issues } : { value: validated }) satisfies Result<Entries>\n}\n\nfunction parseKey(k: string) {\n\tconst optional = k.endsWith('?')\n\n\treturn {\n\t\toptional,\n\t\tkey: optional ? k.slice(0, -1) : k,\n\t}\n}\n\nfunction schemaValueToType(schema: Schema) {\n\tconst fields = Object.entries(schema)\n\t\t.map(([k, v]) => {\n\t\t\tconst { key, optional } = parseKey(k)\n\n\t\t\tlet type: string\n\n\t\t\tif (typeof v === 'string') {\n\t\t\t\ttype = v === 'date' ? 'string' : v\n\t\t\t} else {\n\t\t\t\t// recursively call for record shapes\n\t\t\t\ttype = schemaValueToType(v)\n\t\t\t}\n\n\t\t\treturn `${key}${optional ? '?' : ''}: ${type}`\n\t\t})\n\t\t.join('\\n ')\n\n\treturn `{ ${fields} }`\n}\n\nasync function build(src: Collection[], buildContext: BuildContext) {\n\tconst { logger, outDir } = buildContext\n\tlet names: string[] = []\n\n\t// keep each validated collection beside its schema so emit stays in sync\n\tconst collections: Record<\n\t\tstring,\n\t\t{\n\t\t\titems: Raw[]\n\t\t\tschema: Schema\n\t\t}\n\t> = {}\n\n\ttry {\n\t\tif (!outDir) throw new Error('Output directory is not defined')\n\n\t\t// make sure the output directory exists before the writes begin\n\t\t// that way the emit step can stay simple\n\t\tawait fs.mkdir(outDir, { recursive: true })\n\n\t\t// read and validate every collection before writing anything out\n\t\t// this keeps the js and dts outputs in step\n\t\tfor (const collection of src) {\n\t\t\tconst raw = await create(path.join(process.cwd(), collection.dir), buildContext)\n\n\t\t\t// check each raw item before it makes it into the generated collection\n\t\t\t// bad entries get logged and dropped\n\t\t\tconst validated = await Promise.all(\n\t\t\t\traw.map(async item => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst { body, __mdsrc, ...metadata } = item\n\t\t\t\t\t\tconst res = validate(metadata, collection.schema)\n\n\t\t\t\t\t\tif (res.issues) throw new Error(JSON.stringify(res.issues, null, 2))\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t...res.value,\n\t\t\t\t\t\t\tbody,\n\t\t\t\t\t\t\t__mdsrc,\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t\t`[buildStart]: failed to validate item in ${collection.name}`,\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn null\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tcollections[collection.name] = {\n\t\t\t\t// keep the cleaned items with the schema they came from\n\t\t\t\t// both js and dts generation read from this shape\n\t\t\t\titems: validated.filter(e => e !== null),\n\t\t\t\tschema: collection.schema,\n\t\t\t}\n\t\t}\n\n\t\t// take the collection names after validation has settled\n\t\t// every generated file then works from the same list\n\t\tnames = Object.keys(collections)\n\t\t// queue the file writes first so the emit phase can run together\n\t\t// wait for them once every output is ready\n\t\tconst promises = []\n\n\t\t// build the type file from the schema rather than the observed data\n\t\t// that keeps optional fields and date output honest\n\t\tpromises.push(\n\t\t\tmaybeWrite(\n\t\t\t\tpath.join(outDir, 'types.ts'),\n\t\t\t\t`\n\t\t\t\t\t${names\n\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\tname => `\n\t\t\t\t\t\t\t\texport type ${capitalise(name)} = ${schemaValueToType(collections[name].schema)} & {\n\t\t\t\t\t\t\t\t\tbody: string,\n\t\t\t\t\t\t\t\t\t__mdsrc: {\n\t\t\t\t\t\t\t\t\t\tslug: string\n\t\t\t\t\t\t\t\t\t\tfilename: string\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.join('\\n\\n')}`.trim(),\n\t\t\t),\n\t\t)\n\n\t\t// write the package surface separately so consumers get typed named exports\n\t\t// this mirrors the generated js entry file\n\t\tpromises.push(\n\t\t\tmaybeWrite(\n\t\t\t\tpath.join(outDir, 'index.d.ts'),\n\t\t\t\t`\t\n\t\t\t\t\timport type { ${names.map(name => capitalise(name)).join(', ')} } from './types.js'\n\n\t\t\t\t\t${names\n\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\tname => `\n\t\t\t\t\t\t\t\texport const all${capitalise(pluralise(name, 2))}: ${capitalise(name)}[]\n\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.join('\\n\\n')}\n\n\t\t\t\t\tdeclare module '${PKG_NAME}' {\n\t\t\t\t\t\t${names\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\tname => `\n\t\t\t\t\t\t\t\t\texport const all${capitalise(pluralise(name, 2))}: ${capitalise(name)}[]\n\t\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join('\\n\\n')}\n\t\t\t\t\t}\n\t\t\t\t`.trim(),\n\t\t\t),\n\t\t)\n\n\t\t// serialise each validated collection as a plain module for Vite to load\n\t\tfor (const name of names) {\n\t\t\tconst collection = collections[name]?.items\n\t\t\tconst fileName = toModuleName(name)\n\n\t\t\tpromises.push(\n\t\t\t\tmaybeWrite(\n\t\t\t\t\tpath.join(outDir, `${fileName}.js`),\n\t\t\t\t\t`export const all${capitalise(pluralise(name, 2))} = ${collection?.length ? JSON.stringify(collection) : '[]'}`.trim(),\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\n\t\t// stitch the per-collection modules into the public js entrypoint\n\t\t// this is the file the root package import resolves to\n\t\tpromises.push(\n\t\t\tmaybeWrite(\n\t\t\t\tpath.join(outDir, 'index.js'),\n\t\t\t\tnames.map(name => `export * from './${toModuleName(name)}.js'`).join('\\n'),\n\t\t\t),\n\t\t)\n\n\t\t// flush every generated artifact once all the content is ready\n\t\t// let any failed write fail the build\n\t\tconst writes = await Promise.all(promises)\n\t\tbuildContext.names = names\n\n\t\treturn writes.some(changed => changed)\n\t} catch (err) {\n\t\tlogger.error('[build]: failed to generate data', err)\n\t\tthrow err\n\t}\n}\n\n/**\n * Convert watcher paths to a consistent slash format before comparing them\n */\nfunction normaliseWatchPath(p: string) {\n\treturn p.replace(/\\\\/g, '/')\n}\n\n/**\n * Build the Vite plugin that validates collections and writes the generated modules\n * keep the runtime data and declaration files in the same pass\n * resolve package imports from the generated directory\n */\nexport default function mdsrc(config: PluginConfig): Plugin {\n\tconst src = config.collections\n\n\t// use one logger for the whole build so every step reports the same way\n\t// stay chatty outside production\n\tconst logger = new Logger(\n\t\tconfig.logger?.level ?? (process.env.NODE_ENV === 'production' ? 'error' : 'debug'),\n\t)\n\n\t// write generated files into a hidden folder at the project root\n\t// keep the generated surface out of src\n\tconst outDir = path.join(process.cwd(), GENERATED_DIR)\n\tconst watchRoot = normaliseWatchPath(realpathSync.native(process.cwd()))\n\tconst watchedRoots = src.map(c => `${normaliseWatchPath(path.join(watchRoot, c.dir))}/`)\n\n\t// watcher events can arrive through symlinked paths like /var while cwd has\n\t// already resolved to /private/var, so canonicalise the parent dir once and\n\t// reattach the file name for stable prefix checks\n\tconst resolveWatchFile = (filePath: string) => {\n\t\tconst absolutePath = path.resolve(watchRoot, filePath)\n\t\tconst parentPath = path.dirname(absolutePath)\n\n\t\ttry {\n\t\t\tconst resolvedParentPath = normaliseWatchPath(realpathSync.native(parentPath))\n\t\t\treturn normaliseWatchPath(\n\t\t\t\tpath.join(resolvedParentPath, path.basename(absolutePath)),\n\t\t\t)\n\t\t} catch {\n\t\t\treturn normaliseWatchPath(absolutePath)\n\t\t}\n\t}\n\n\tfunction watchedFile(filePath: string) {\n\t\treturn watchedRoots.some(root => resolveWatchFile(filePath).startsWith(root))\n\t}\n\n\t// pass shared build tools into helpers without dragging lots of state around\n\t// keep the helper signatures small\n\tconst buildContext = {\n\t\tlogger,\n\t\tcompileOptions: config.compileOptions,\n\t\toutDir,\n\t\tnames: [],\n\t} satisfies BuildContext\n\n\tlet rebuildRunning = false\n\tlet rebuildQueued = false\n\tlet rebuildReason = 'change'\n\n\tconst rebuild = debounce((event: string, filePath: string) => {\n\t\tfunction queue() {\n\t\t\tvoid (async () => {\n\t\t\t\t// collapse bursts of file events into one active rebuild plus a single\n\t\t\t\t// queued rerun when changes land mid-build\n\t\t\t\tif (rebuildRunning) {\n\t\t\t\t\trebuildQueued = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\trebuildRunning = true\n\n\t\t\t\tdo {\n\t\t\t\t\trebuildQueued = false\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst changed = await build(src, buildContext)\n\n\t\t\t\t\t\tif (changed) logger.info(`[watch]: content rebuilt (${rebuildReason})`)\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlogger.error('[watch] content rebuild failed', err)\n\t\t\t\t\t}\n\t\t\t\t} while (rebuildQueued)\n\n\t\t\t\trebuildRunning = false\n\t\t\t})()\n\t\t}\n\n\t\t// ignore anything outside the watched content dirs\n\t\tif (!watchedFile(filePath)) return\n\n\t\tconst file = resolveWatchFile(filePath)\n\n\t\trebuildReason = `${event}: ${path.relative(watchRoot, file)}`\n\t\tqueue()\n\t}, 75)\n\n\treturn {\n\t\tname: 'mdsrc',\n\t\tenforce: 'pre',\n\t\tconfig(viteConfig) {\n\t\t\tviteConfig.optimizeDeps ??= {}\n\t\t\tviteConfig.optimizeDeps.exclude = [\n\t\t\t\t...new Set([...(viteConfig.optimizeDeps.exclude ?? []), PKG_NAME]),\n\t\t\t]\n\n\t\t\tviteConfig.resolve ??= {}\n\n\t\t\tif (Array.isArray(viteConfig.resolve.alias)) {\n\t\t\t\tviteConfig.resolve.alias = [\n\t\t\t\t\t...viteConfig.resolve.alias,\n\t\t\t\t\t{\n\t\t\t\t\t\tfind: PKG_NAME,\n\t\t\t\t\t\treplacement: path.join(outDir, 'index.js'),\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t} else {\n\t\t\t\tviteConfig.resolve.alias = {\n\t\t\t\t\t...viteConfig.resolve.alias,\n\t\t\t\t\t[PKG_NAME]: path.join(outDir, 'index.js'),\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tasync buildStart() {\n\t\t\tawait build(src, buildContext)\n\t\t},\n\t\tconfigureServer(server: ViteDevServer) {\n\t\t\tlogger.info(\n\t\t\t\t`[configureServer]: Watching for changes in ./${src.map(c => c.dir).join(', ')}...`,\n\t\t\t)\n\n\t\t\tserver.watcher\n\t\t\t\t.on('add', (p: string) => rebuild('add', p))\n\t\t\t\t.on('change', (p: string) => rebuild('change', p))\n\t\t\t\t.on('unlink', (p: string) => rebuild('unlink', p))\n\t\t},\n\t\tresolveId(id) {\n\t\t\t// point imports at generated files rather than source files\n\t\t\t// that makes the package behave like a normal module\n\t\t\tif (id === PKG_NAME) {\n\t\t\t\treturn path.join(outDir, 'index.js')\n\t\t\t}\n\n\t\t\tif (id.startsWith(`${PKG_NAME}/`)) {\n\t\t\t\t// allow collection subpath imports once the build knows their names\n\t\t\t\t// leave unknown subpaths unresolved\n\t\t\t\tconst subpath = id.slice(PKG_NAME.length + 1)\n\t\t\t\tconst match = buildContext.names.find(\n\t\t\t\t\tname => name === subpath || toModuleName(name) === subpath,\n\t\t\t\t)\n\n\t\t\t\tif (match) {\n\t\t\t\t\treturn path.join(outDir, `${toModuleName(match)}.js`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null\n\t\t},\n\t}\n}\n\nfunction toModuleName(name: string) {\n\treturn name.toLowerCase()\n}\n\nif (import.meta.vitest) {\n\tconst { it, expect, describe } = import.meta.vitest\n\n\tconst now = Date.now()\n\n\tconst yaml = {\n\t\tkind: 'yaml',\n\t\tvalue: dedent(`\n\t\t\ttitle: mdsrc\n\t\t\tdate: ${now}\n\t\t`),\n\t} satisfies MarkdownToHtmlResult['frontmatter']\n\n\tconst toml = {\n\t\tkind: 'toml',\n\t\tvalue: dedent(`\n\t\t\ttitle = \"mdsrc\"\n\t\t\tdate = ${now}\n\t\t`),\n\t} satisfies MarkdownToHtmlResult['frontmatter']\n\n\tdescribe('markdown parsing', () => {\n\t\tit('parses frontmatter', async () => {\n\t\t\tfor (const f of [yaml, toml]) {\n\t\t\t\tconst frontmatter = await parse(f)\n\n\t\t\t\texpect(frontmatter).toEqual({\n\t\t\t\t\ttitle: 'mdsrc',\n\t\t\t\t\tdate: now,\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\n\t\tit('returns empty object for missing frontmatter', async () => {\n\t\t\tconst frontmatter = await parse(null)\n\t\t\texpect(frontmatter).toEqual({})\n\t\t})\n\t})\n}\n",
6
6
  "export const NAME = 'mdsrc'\nexport const PKG_NAME = `@jk2908/${NAME}`\nexport const GENERATED_DIR = `.${NAME}`",
7
7
  "import { NAME } from './config.js'\n\nconst LEVELS = {\n\tdebug: 0,\n\tinfo: 1,\n\twarn: 2,\n\terror: 3,\n\tfatal: 4,\n} as const\n\nexport type LogLevel = keyof typeof LEVELS\n\ntype LogEntry = {\n\tts: number\n\tlevel: LogLevel\n\tmessage: string\n\terror?:\n\t\t| Error\n\t\t| {\n\t\t\t\tmessage: string\n\t\t\t\tstack?: string\n\t\t\t\tcause?: unknown\n\t\t }\n}\n\n/**\n * Log messages with different severity levels\n */\nexport class Logger {\n\tstatic #defaultLevel: LogLevel = 'info'\n\n\t#level?: LogLevel\n\n\tconstructor(level?: LogLevel) {\n\t\tthis.#level = level\n\t}\n\n\tstatic set defaultLevel(level: LogLevel) {\n\t\tLogger.#defaultLevel = level\n\t}\n\n\tstatic get defaultLevel() {\n\t\treturn Logger.#defaultLevel\n\t}\n\n\t/**\n\t * Convert a value to an Error instance\n\t */\n\tstatic toError(err: unknown) {\n\t\treturn err instanceof Error ? err : new Error(String(err), { cause: err })\n\t}\n\n\t/**\n\t * Stringify the error for logging\n\t */\n\tstatic print(err: unknown) {\n\t\tif (err instanceof Error) {\n\t\t\treturn err.message + (err.stack ? `\\n${err.stack}` : '')\n\t\t}\n\n\t\t// for plain objects, attempt to stringify with indentation\n\t\t// for readability\n\t\tif (typeof err === 'object' && err !== null) {\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(err, null, 2)\n\t\t\t} catch {\n\t\t\t\t// if stringify fails (e.g. circular reference), fall back\n\t\t\t\t// to basic string conversion\n\t\t\t\treturn String(err)\n\t\t\t}\n\t\t}\n\n\t\treturn String(err)\n\t}\n\n\tset level(level: LogLevel) {\n\t\tthis.#level = level\n\t}\n\n\tget level() {\n\t\treturn this.#level ?? Logger.#defaultLevel\n\t}\n\n\t/**\n\t * Log a message with a specific level\n\t */\n\tlog(level: LogLevel, message: string, error?: Error) {\n\t\tif (LEVELS[level] < LEVELS[this.level]) return\n\n\t\tconst entry: LogEntry = {\n\t\t\tts: Date.now(),\n\t\t\tlevel,\n\t\t\tmessage,\n\t\t}\n\n\t\tif (level === 'error' || level === 'fatal') {\n\t\t\tentry.error = error ? Logger.toError(error) : new Error(message)\n\t\t}\n\n\t\tconst line = `[${NAME}] [${entry.ts}] [${level.toUpperCase()}] ${message}`\n\t\tconst extra = entry.error ? `\\n${Logger.print(entry.error)}` : ''\n\n\t\tif (level === 'warn') {\n\t\t\tconsole.warn(line, extra)\n\t\t\treturn\n\t\t}\n\n\t\tif (level === 'error' || level === 'fatal') {\n\t\t\tconsole.error(line, extra)\n\t\t\treturn\n\t\t}\n\n\t\tconsole.log(line, extra)\n\t}\n\n\t/**\n\t * Log a debug message\n\t */\n\tdebug(...messages: string[]) {\n\t\tthis.log('debug', messages.join(' '))\n\t}\n\n\t/**\n\t * Log an info message\n\t */\n\tinfo(...messages: string[]) {\n\t\tthis.log('info', messages.join(' '))\n\t}\n\n\t/**\n\t * Log a warning message\n\t */\n\twarn(...messages: string[]) {\n\t\tthis.log('warn', messages.join(' '))\n\t}\n\n\t/**\n\t * Log an error message\n\t */\n\terror(message: string, error?: unknown) {\n\t\tthis.log('error', message, error === undefined ? undefined : Logger.toError(error))\n\t}\n\n\t/**\n\t * Log a fatal error message\n\t */\n\tfatal(message: string, error?: unknown) {\n\t\tthis.log('fatal', message, error === undefined ? undefined : Logger.toError(error))\n\t}\n}\n\nexport const logger = new Logger(\n\tprocess.env.NODE_ENV === 'production' ? 'error' : 'debug',\n)\n",
8
- "export function capitalise(str: string) {\n\treturn str.charAt(0).toUpperCase() + str.slice(1)\n}\n\nexport function pluralise(str: string, count: number) {\n\treturn count === 1 ? str : str.endsWith('s') ? str : `${str}s`\n}\n\nexport function debounce<T extends unknown[]>(fn: (...args: T) => void, wait: number) {\n\tlet timeoutId: ReturnType<typeof setTimeout> | null = null\n\n\treturn (...args: T) => {\n\t\tif (timeoutId) {\n\t\t\tclearTimeout(timeoutId)\n\t\t}\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tfn.apply(null, args)\n\t\t}, wait)\n\t}\n}\n"
8
+ "export function capitalise(str: string) {\n\treturn str.charAt(0).toUpperCase() + str.slice(1)\n}\n\nexport function pluralise(str: string, count: number) {\n\treturn count === 1 ? str : str.endsWith('s') ? str : `${str}s`\n}\n\nexport function debounce<T extends unknown[]>(fn: (...args: T) => void, wait: number) {\n\tlet timeoutId: ReturnType<typeof setTimeout> | null = null\n\n\treturn (...args: T) => {\n\t\tif (timeoutId) {\n\t\t\tclearTimeout(timeoutId)\n\t\t}\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tfn.apply(null, args)\n\t\t}, wait)\n\t}\n}\n\nexport function dedent(str: string) {\n\treturn str\n\t\t.replace(/^\\n/, '')\n\t\t.replace(/\\s+$/, '')\n\t\t.split('\\n')\n\t\t.filter(Boolean)\n\t\t.map(line => line.replace(/^\\s+/, ''))\n\t\t.join('\\n')\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nexport function deep(obj: any, path: string, value: unknown) {\n\tconst parts = path.split('.')\n\tlet cur = obj\n\n\tfor (let i = 0; i < parts.length - 1; i++) {\n\t\tconst k = parts[i]\n\t\tcur[k] ??= {}\n\t\tcur = cur[k]\n\t}\n\n\tcur[parts.at(-1)!] = value\n}\n"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "4A1919CF4AE2D2C864756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "0F1D108961D2552964756E2164756E21",
12
12
  "names": []
13
13
  }
package/dist/logger.d.ts CHANGED
@@ -51,3 +51,4 @@ export declare class Logger {
51
51
  }
52
52
  export declare const logger: Logger;
53
53
  export {};
54
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,MAAM;aACX,KAAK,EAAE,CAAC;aACR,IAAI,EAAE,CAAC;aACP,IAAI,EAAE,CAAC;aACP,KAAK,EAAE,CAAC;aACR,KAAK,EAAE,CAAC;CACC,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,MAAM,CAAA;AAe1C;;GAEG;AACH,qBAAa,MAAM;;IAKlB,YAAY,KAAK,CAAC,EAAE,QAAQ,EAE3B;IAED,MAAM,KAAK,YAAY,CAAC,KAAK,EAAE,QAAQ,EAEtC;IAED,MAAM,KAAK,YAAY,IAJQ,QAAQ,CAMtC;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,SAE1B;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,UAkBxB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,QAAQ,EAExB;IAED,IAAI,KAAK,IAJQ,QAAQ,CAMxB;IAED;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,QA2BlD;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,QAE1B;IAED;;OAEG;IACH,IAAI,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,QAEzB;IAED;;OAEG;IACH,IAAI,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,QAEzB;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,QAErC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,QAErC;CACD;AAED,eAAO,MAAM,MAAM,QAElB,CAAA"}
package/dist/logger.js ADDED
@@ -0,0 +1,115 @@
1
+ import { NAME } from './config.js';
2
+ const LEVELS = {
3
+ debug: 0,
4
+ info: 1,
5
+ warn: 2,
6
+ error: 3,
7
+ fatal: 4,
8
+ };
9
+ /**
10
+ * Log messages with different severity levels
11
+ */
12
+ export class Logger {
13
+ static #defaultLevel = 'info';
14
+ #level;
15
+ constructor(level) {
16
+ this.#level = level;
17
+ }
18
+ static set defaultLevel(level) {
19
+ Logger.#defaultLevel = level;
20
+ }
21
+ static get defaultLevel() {
22
+ return Logger.#defaultLevel;
23
+ }
24
+ /**
25
+ * Convert a value to an Error instance
26
+ */
27
+ static toError(err) {
28
+ return err instanceof Error ? err : new Error(String(err), { cause: err });
29
+ }
30
+ /**
31
+ * Stringify the error for logging
32
+ */
33
+ static print(err) {
34
+ if (err instanceof Error) {
35
+ return err.message + (err.stack ? `\n${err.stack}` : '');
36
+ }
37
+ // for plain objects, attempt to stringify with indentation
38
+ // for readability
39
+ if (typeof err === 'object' && err !== null) {
40
+ try {
41
+ return JSON.stringify(err, null, 2);
42
+ }
43
+ catch {
44
+ // if stringify fails (e.g. circular reference), fall back
45
+ // to basic string conversion
46
+ return String(err);
47
+ }
48
+ }
49
+ return String(err);
50
+ }
51
+ set level(level) {
52
+ this.#level = level;
53
+ }
54
+ get level() {
55
+ return this.#level ?? Logger.#defaultLevel;
56
+ }
57
+ /**
58
+ * Log a message with a specific level
59
+ */
60
+ log(level, message, error) {
61
+ if (LEVELS[level] < LEVELS[this.level])
62
+ return;
63
+ const entry = {
64
+ ts: Date.now(),
65
+ level,
66
+ message,
67
+ };
68
+ if (level === 'error' || level === 'fatal') {
69
+ entry.error = error ? Logger.toError(error) : new Error(message);
70
+ }
71
+ const line = `[${NAME}] [${entry.ts}] [${level.toUpperCase()}] ${message}`;
72
+ const extra = entry.error ? `\n${Logger.print(entry.error)}` : '';
73
+ if (level === 'warn') {
74
+ console.warn(line, extra);
75
+ return;
76
+ }
77
+ if (level === 'error' || level === 'fatal') {
78
+ console.error(line, extra);
79
+ return;
80
+ }
81
+ console.log(line, extra);
82
+ }
83
+ /**
84
+ * Log a debug message
85
+ */
86
+ debug(...messages) {
87
+ this.log('debug', messages.join(' '));
88
+ }
89
+ /**
90
+ * Log an info message
91
+ */
92
+ info(...messages) {
93
+ this.log('info', messages.join(' '));
94
+ }
95
+ /**
96
+ * Log a warning message
97
+ */
98
+ warn(...messages) {
99
+ this.log('warn', messages.join(' '));
100
+ }
101
+ /**
102
+ * Log an error message
103
+ */
104
+ error(message, error) {
105
+ this.log('error', message, error === undefined ? undefined : Logger.toError(error));
106
+ }
107
+ /**
108
+ * Log a fatal error message
109
+ */
110
+ fatal(message, error) {
111
+ this.log('fatal', message, error === undefined ? undefined : Logger.toError(error));
112
+ }
113
+ }
114
+ export const logger = new Logger(process.env.NODE_ENV === 'production' ? 'error' : 'debug');
115
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAElC,MAAM,MAAM,GAAG;IACd,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;CACC,CAAA;AAiBV;;GAEG;AACH,MAAM,OAAO,MAAM;IAClB,MAAM,CAAC,aAAa,GAAa,MAAM,CAAA;IAEvC,MAAM,CAAW;IAEjB,YAAY,KAAgB;QAC3B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;IACpB,CAAC;IAED,MAAM,KAAK,YAAY,CAAC,KAAe;QACtC,MAAM,CAAC,aAAa,GAAG,KAAK,CAAA;IAC7B,CAAC;IAED,MAAM,KAAK,YAAY;QACtB,OAAO,MAAM,CAAC,aAAa,CAAA;IAC5B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,GAAY;QAC1B,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAC3E,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,GAAY;QACxB,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;YAC1B,OAAO,GAAG,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACzD,CAAC;QAED,2DAA2D;QAC3D,kBAAkB;QAClB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACJ,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;YACpC,CAAC;YAAC,MAAM,CAAC;gBACR,0DAA0D;gBAC1D,6BAA6B;gBAC7B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAA;YACnB,CAAC;QACF,CAAC;QAED,OAAO,MAAM,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,IAAI,KAAK,CAAC,KAAe;QACxB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;IACpB,CAAC;IAED,IAAI,KAAK;QACR,OAAO,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,aAAa,CAAA;IAC3C,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,KAAa;QAClD,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAM;QAE9C,MAAM,KAAK,GAAa;YACvB,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,KAAK;YACL,OAAO;SACP,CAAA;QAED,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YAC5C,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;QACjE,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,IAAI,MAAM,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC,WAAW,EAAE,KAAK,OAAO,EAAE,CAAA;QAC1E,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAEjE,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YACzB,OAAM;QACP,CAAC;QAED,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YAC5C,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAC1B,OAAM;QACP,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,QAAkB;QAC1B,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtC,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,GAAG,QAAkB;QACzB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACrC,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,GAAG,QAAkB;QACzB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACrC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAe,EAAE,KAAe;QACrC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IACpF,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAe,EAAE,KAAe;QACrC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IACpF,CAAC;CACD;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,MAAM,CAC/B,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CACzD,CAAA"}
package/dist/types.d.ts CHANGED
@@ -1,31 +1,30 @@
1
- import type MarkdownIt from 'markdown-it';
1
+ import type { CompileOptions as SatteriCompileOptions } from 'satteri';
2
2
  import type { LogLevel, Logger } from './logger.js';
3
- type MarkdownItUseArgs = Parameters<MarkdownIt['use']>;
4
- export type MarkdownPlugin = MarkdownItUseArgs[0];
5
- export type MarkdownPluginUse = MarkdownPlugin | readonly [
6
- plugin: MarkdownPlugin,
7
- ...params: MarkdownItUseArgs extends [unknown, ...infer Params] ? Params : never
8
- ];
3
+ export type CompileOptions = SatteriCompileOptions;
9
4
  export type PluginConfig = {
10
5
  collections: Collection[];
11
6
  logger?: {
12
7
  level?: LogLevel;
13
8
  };
14
- plugins?: MarkdownPluginUse[];
9
+ compileOptions?: CompileOptions;
15
10
  };
16
11
  export type BuildContext = {
17
12
  logger: InstanceType<typeof Logger>;
18
- plugins?: MarkdownPluginUse[];
13
+ compileOptions?: CompileOptions;
19
14
  outDir?: string;
20
15
  names?: string[];
21
16
  };
22
- export type SchemaEntry = {
23
- type: 'string' | 'number' | 'boolean' | 'date';
24
- optional?: boolean;
25
- minLength?: number;
26
- maxLength?: number;
27
- };
28
- export type Schema = Record<string, SchemaEntry>;
17
+ export declare namespace Schema {
18
+ type Primitive = 'string' | 'number' | 'boolean' | 'date' | 'object';
19
+ export type Key = string;
20
+ export type Value = Primitive | {
21
+ [key: Key]: Value;
22
+ };
23
+ export {};
24
+ }
25
+ export interface Schema {
26
+ [key: Schema.Key]: Schema.Value;
27
+ }
29
28
  export type Collection = {
30
29
  name: string;
31
30
  dir: string;
@@ -37,8 +36,7 @@ export type Raw = {
37
36
  slug: string;
38
37
  filename: string;
39
38
  };
40
- html: string;
41
- markdown: string;
39
+ body: string;
42
40
  } & Entries;
43
41
  export type Types = Record<string, string>;
44
42
  export type Issue = {
@@ -53,4 +51,4 @@ export type Success<Output> = {
53
51
  readonly types?: Record<string, string>;
54
52
  };
55
53
  export type Result<Output> = Success<Output> | Fail;
56
- export {};
54
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,IAAI,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAEtE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEnD,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAA;AAElD,MAAM,MAAM,YAAY,GAAG;IAC1B,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,MAAM,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,QAAQ,CAAA;KAChB,CAAA;IACD,cAAc,CAAC,EAAE,cAAc,CAAA;CAC/B,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,YAAY,CAAC,OAAO,MAAM,CAAC,CAAA;IACnC,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAED,yBAAiB,MAAM,CAAC,CAAC;IACxB,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;IAEpE,MAAM,MAAM,GAAG,GAAG,MAAM,CAAA;IACxB,MAAM,MAAM,KAAK,GAAG,SAAS,GAAG;QAAE,CAAC,GAAG,EAAE,GAAG,GAAG,KAAK,CAAA;KAAE,CAAA;;CACrD;AAED,MAAM,WAAW,MAAM;IACtB,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,KAAK,CAAA;CAC/B;AAED,MAAM,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE7C,MAAM,MAAM,GAAG,GAAG;IACjB,OAAO,EAAE;QACR,IAAI,EAAE,MAAM,CAAA;QACZ,QAAQ,EAAE,MAAM,CAAA;KAChB,CAAA;IACD,IAAI,EAAE,MAAM,CAAA;CACZ,GAAG,OAAO,CAAA;AAEX,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE1C,MAAM,MAAM,KAAK,GAAG;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,IAAI,GAAG;IAClB,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,OAAO,CAAC,MAAM,IAAI;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  export declare function capitalise(str: string): string;
2
2
  export declare function pluralise(str: string, count: number): string;
3
3
  export declare function debounce<T extends unknown[]>(fn: (...args: T) => void, wait: number): (...args: T) => void;
4
+ export declare function dedent(str: string): string;
5
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
6
+ export declare function deep(obj: any, path: string, value: unknown): void;
7
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,UAErC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAEnD;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,MAAM,aAGlE,CAAC,UASlB;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,UAQjC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,QAW1D"}
package/dist/utils.js ADDED
@@ -0,0 +1,40 @@
1
+ export function capitalise(str) {
2
+ return str.charAt(0).toUpperCase() + str.slice(1);
3
+ }
4
+ export function pluralise(str, count) {
5
+ return count === 1 ? str : str.endsWith('s') ? str : `${str}s`;
6
+ }
7
+ export function debounce(fn, wait) {
8
+ let timeoutId = null;
9
+ return (...args) => {
10
+ if (timeoutId) {
11
+ clearTimeout(timeoutId);
12
+ }
13
+ timeoutId = setTimeout(() => {
14
+ fn.apply(null, args);
15
+ }, wait);
16
+ };
17
+ }
18
+ export function dedent(str) {
19
+ return str
20
+ .replace(/^\n/, '')
21
+ .replace(/\s+$/, '')
22
+ .split('\n')
23
+ .filter(Boolean)
24
+ .map(line => line.replace(/^\s+/, ''))
25
+ .join('\n');
26
+ }
27
+ export function isRecord(value) {
28
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
29
+ }
30
+ export function deep(obj, path, value) {
31
+ const parts = path.split('.');
32
+ let cur = obj;
33
+ for (let i = 0; i < parts.length - 1; i++) {
34
+ const k = parts[i];
35
+ cur[k] ??= {};
36
+ cur = cur[k];
37
+ }
38
+ cur[parts.at(-1)] = value;
39
+ }
40
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,GAAW;IACrC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,KAAa;IACnD,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAsB,EAAwB,EAAE,IAAY;IACnF,IAAI,SAAS,GAAyC,IAAI,CAAA;IAE1D,OAAO,CAAC,GAAG,IAAO,EAAE,EAAE;QACrB,IAAI,SAAS,EAAE,CAAC;YACf,YAAY,CAAC,SAAS,CAAC,CAAA;QACxB,CAAC;QAED,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAC3B,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACrB,CAAC,EAAE,IAAI,CAAC,CAAA;IACT,CAAC,CAAA;AACF,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,GAAW;IACjC,OAAO,GAAG;SACR,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;SAClB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;SACnB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;SACrC,IAAI,CAAC,IAAI,CAAC,CAAA;AACb,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC5E,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAQ,EAAE,IAAY,EAAE,KAAc;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC7B,IAAI,GAAG,GAAG,GAAG,CAAA;IAEb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QAClB,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;QACb,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;IACb,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,GAAG,KAAK,CAAA;AAC3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jk2908/mdsrc",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "author": "Jerome Kenway",
6
6
  "description": "A Vite plugin for turning structured Markdown content into importable, type-safe modules",
@@ -11,12 +11,18 @@
11
11
  "main": "./dist/index.js",
12
12
  "types": "./dist/index.d.ts",
13
13
  "scripts": {
14
- "build": "bun run ./build.ts && bun x tsc --project tsconfig.json --emitDeclarationOnly",
15
- "test": "cd ../.. && bun test"
14
+ "build": "rm -rf dist && tsgo && bun run ./build.ts",
15
+ "test": "bunx vitest"
16
16
  },
17
17
  "dependencies": {
18
- "@types/markdown-it": "^14.1.2",
19
- "markdown-it": "^14.1.0"
18
+ "satteri": "^0.8.1",
19
+ "smol-toml": "^1.6.1",
20
+ "yaml": "^2.9.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/bun": "^1.3.14",
24
+ "@types/node": "^25.9.3",
25
+ "vitest": "^4.1.9"
20
26
  },
21
27
  "peerDependencies": {
22
28
  "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"