@contentrain/mcp 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +93 -25
  2. package/dist/annotations-Clhbw2lh.mjs +126 -0
  3. package/dist/annotations-Clhbw2lh.mjs.map +1 -0
  4. package/dist/{apply-manager-BkPMfpsN.mjs → apply-manager-D5ym5Jba.mjs} +7 -7
  5. package/dist/{apply-manager-BkPMfpsN.mjs.map → apply-manager-D5ym5Jba.mjs.map} +1 -1
  6. package/dist/{astro-parser-BJ-54JGe.mjs → astro-parser-B-nLl0zE.mjs} +3 -3
  7. package/dist/{astro-parser-BJ-54JGe.mjs.map → astro-parser-B-nLl0zE.mjs.map} +1 -1
  8. package/dist/{branch-lifecycle-wGJg25LT.mjs → branch-lifecycle-CFVpTpNT.mjs} +33 -3
  9. package/dist/{branch-lifecycle-wGJg25LT.mjs.map → branch-lifecycle-CFVpTpNT.mjs.map} +1 -1
  10. package/dist/{compiler-rIC4ETHv.mjs → compiler-k4yttlzM.mjs} +2 -2
  11. package/dist/{compiler-rIC4ETHv.mjs.map → compiler-k4yttlzM.mjs.map} +1 -1
  12. package/dist/{compiler-sfc.cjs-BbD04rn_.mjs → compiler-sfc.cjs-D0d_OicF.mjs} +2 -2
  13. package/dist/{compiler-sfc.cjs-BbD04rn_.mjs.map → compiler-sfc.cjs-D0d_OicF.mjs.map} +1 -1
  14. package/dist/config-BIl5Ybj0.mjs +40 -0
  15. package/dist/config-BIl5Ybj0.mjs.map +1 -0
  16. package/dist/content-manager-DqDLSNQ-.d.mts +63 -0
  17. package/dist/content-manager-DqDLSNQ-.d.mts.map +1 -0
  18. package/dist/context-J0OU9Nfp.mjs +102 -0
  19. package/dist/context-J0OU9Nfp.mjs.map +1 -0
  20. package/dist/contracts-DfL0BfrD.mjs +2 -0
  21. package/dist/core/apply-manager.mjs +10 -10
  22. package/dist/core/config.d.mts +13 -0
  23. package/dist/core/config.d.mts.map +1 -1
  24. package/dist/core/config.mjs +2 -3
  25. package/dist/core/content-manager.d.mts +2 -45
  26. package/dist/core/content-manager.mjs +3 -4
  27. package/dist/core/context.d.mts +32 -2
  28. package/dist/core/context.d.mts.map +1 -1
  29. package/dist/core/context.mjs +7 -7
  30. package/dist/core/contracts/index.d.mts +2 -0
  31. package/dist/core/contracts/index.mjs +2 -0
  32. package/dist/core/doctor.d.mts +76 -0
  33. package/dist/core/doctor.d.mts.map +1 -0
  34. package/dist/core/doctor.mjs +8 -0
  35. package/dist/core/graph-builder.mjs +3 -4
  36. package/dist/core/meta-manager.mjs +2 -3
  37. package/dist/core/model-manager.d.mts +34 -1
  38. package/dist/core/model-manager.d.mts.map +1 -1
  39. package/dist/core/model-manager.mjs +3 -4
  40. package/dist/core/ops/index.d.mts +137 -0
  41. package/dist/core/ops/index.d.mts.map +1 -0
  42. package/dist/core/ops/index.mjs +4 -0
  43. package/dist/core/overlay-reader.d.mts +42 -0
  44. package/dist/core/overlay-reader.d.mts.map +1 -0
  45. package/dist/core/overlay-reader.mjs +2 -0
  46. package/dist/core/scan-config.mjs +2 -3
  47. package/dist/core/scanner.mjs +4 -5
  48. package/dist/core/validator/index.d.mts +129 -0
  49. package/dist/core/validator/index.d.mts.map +1 -0
  50. package/dist/core/validator/index.mjs +6 -0
  51. package/dist/{detect-DinOQAmK.mjs → detect-DL9mFmaS.mjs} +2 -2
  52. package/dist/{detect-DinOQAmK.mjs.map → detect-DL9mFmaS.mjs.map} +1 -1
  53. package/dist/doctor-CgBoT6h1.mjs +338 -0
  54. package/dist/doctor-CgBoT6h1.mjs.map +1 -0
  55. package/dist/{fs-BnIQ4dKj.mjs → fs-DLbVB-Ek.mjs} +2 -2
  56. package/dist/fs-DLbVB-Ek.mjs.map +1 -0
  57. package/dist/git/branch-lifecycle.d.mts +30 -1
  58. package/dist/git/branch-lifecycle.d.mts.map +1 -1
  59. package/dist/git/branch-lifecycle.mjs +4 -5
  60. package/dist/git/transaction.d.mts +2 -2
  61. package/dist/git/transaction.d.mts.map +1 -1
  62. package/dist/git/transaction.mjs +8 -8
  63. package/dist/{graph-builder-tM7zj-SX.mjs → graph-builder-DXv2JEp-.mjs} +3 -3
  64. package/dist/{graph-builder-tM7zj-SX.mjs.map → graph-builder-DXv2JEp-.mjs.map} +1 -1
  65. package/dist/{id-DV_T9Ic8.mjs → id-CIO9s0x_.mjs} +1 -1
  66. package/dist/{id-DV_T9Ic8.mjs.map → id-CIO9s0x_.mjs.map} +1 -1
  67. package/dist/index-D0wQJSgs.d.mts +2 -0
  68. package/dist/index.mjs +22 -17
  69. package/dist/index.mjs.map +1 -1
  70. package/dist/local-DvP4Y4T-.mjs +182 -0
  71. package/dist/local-DvP4Y4T-.mjs.map +1 -0
  72. package/dist/{meta-manager--0IwAPCl.mjs → meta-manager-C900rEyG.mjs} +2 -2
  73. package/dist/{meta-manager--0IwAPCl.mjs.map → meta-manager-C900rEyG.mjs.map} +1 -1
  74. package/dist/{model-manager-DW_fOGRM.mjs → model-manager-BhLsUgaB.mjs} +241 -49
  75. package/dist/model-manager-BhLsUgaB.mjs.map +1 -0
  76. package/dist/{node-Cbq5eR5i.mjs → node-DN9hZeil.mjs} +1 -1
  77. package/dist/{node-Cbq5eR5i.mjs.map → node-DN9hZeil.mjs.map} +1 -1
  78. package/dist/ops-DjEWi5Wa.mjs +486 -0
  79. package/dist/ops-DjEWi5Wa.mjs.map +1 -0
  80. package/dist/overlay-reader-DOdyDRIv.mjs +74 -0
  81. package/dist/overlay-reader-DOdyDRIv.mjs.map +1 -0
  82. package/dist/paths-C3xNyc7z.mjs +52 -0
  83. package/dist/paths-C3xNyc7z.mjs.map +1 -0
  84. package/dist/paths-CYl5vCBD.mjs +54 -0
  85. package/dist/paths-CYl5vCBD.mjs.map +1 -0
  86. package/dist/providers/github/index.d.mts +144 -0
  87. package/dist/providers/github/index.d.mts.map +1 -0
  88. package/dist/providers/github/index.mjs +414 -0
  89. package/dist/providers/github/index.mjs.map +1 -0
  90. package/dist/providers/gitlab/index.d.mts +158 -0
  91. package/dist/providers/gitlab/index.d.mts.map +1 -0
  92. package/dist/providers/gitlab/index.mjs +361 -0
  93. package/dist/providers/gitlab/index.mjs.map +1 -0
  94. package/dist/providers/local/index.d.mts +106 -0
  95. package/dist/providers/local/index.d.mts.map +1 -0
  96. package/dist/providers/local/index.mjs +12 -0
  97. package/dist/{scan-config-QgnJ1-52.mjs → scan-config-jC6XmiqF.mjs} +2 -2
  98. package/dist/{scan-config-QgnJ1-52.mjs.map → scan-config-jC6XmiqF.mjs.map} +1 -1
  99. package/dist/{scanner-CQOxARUU.mjs → scanner-DKMwc03k.mjs} +26 -8
  100. package/dist/scanner-DKMwc03k.mjs.map +1 -0
  101. package/dist/serialization-B1CEzR4H.mjs +2 -0
  102. package/dist/server/http/index.d.mts +65 -0
  103. package/dist/server/http/index.d.mts.map +1 -0
  104. package/dist/server/http/index.mjs +137 -0
  105. package/dist/server/http/index.mjs.map +1 -0
  106. package/dist/{server-DfWRpzdP.mjs → server-B1OJrUQ9.mjs} +486 -355
  107. package/dist/server-B1OJrUQ9.mjs.map +1 -0
  108. package/dist/server-WqGx6ait.d.mts +50 -0
  109. package/dist/server-WqGx6ait.d.mts.map +1 -0
  110. package/dist/server.d.mts +2 -7
  111. package/dist/server.mjs +22 -17
  112. package/dist/{source-map-generator-DpO-ejrt.mjs → source-map-generator-BWDva49I.mjs} +1 -1
  113. package/dist/{source-map-generator-DpO-ejrt.mjs.map → source-map-generator-BWDva49I.mjs.map} +1 -1
  114. package/dist/{svelte-parser-1bBcMx3T.mjs → svelte-parser-Dr9mmZuk.mjs} +3 -3
  115. package/dist/{svelte-parser-1bBcMx3T.mjs.map → svelte-parser-Dr9mmZuk.mjs.map} +1 -1
  116. package/dist/templates/index.mjs +1 -1
  117. package/dist/{templates-bRPzYq4M.mjs → templates-VrLVqfe5.mjs} +1 -1
  118. package/dist/{templates-bRPzYq4M.mjs.map → templates-VrLVqfe5.mjs.map} +1 -1
  119. package/dist/tools/annotations.d.mts +24 -0
  120. package/dist/tools/annotations.d.mts.map +1 -0
  121. package/dist/tools/annotations.mjs +2 -0
  122. package/dist/{transaction-C_Pvux78.mjs → transaction-DgkNgWSI.mjs} +61 -32
  123. package/dist/transaction-DgkNgWSI.mjs.map +1 -0
  124. package/dist/tsx-parser-BOMZE_yQ.mjs +2 -0
  125. package/dist/{tsx-parser-BWiMGAIR.mjs → tsx-parser-CQc6nr-o.mjs} +1 -1
  126. package/dist/{tsx-parser-BWiMGAIR.mjs.map → tsx-parser-CQc6nr-o.mjs.map} +1 -1
  127. package/dist/util/detect.mjs +2 -3
  128. package/dist/util/fs.mjs +1 -2
  129. package/dist/util/id.mjs +1 -1
  130. package/dist/validator-B0nNRFeU.mjs +906 -0
  131. package/dist/validator-B0nNRFeU.mjs.map +1 -0
  132. package/dist/{vue-parser-DTPTN3Yb.mjs → vue-parser-COgjArqv.mjs} +3 -3
  133. package/dist/{vue-parser-DTPTN3Yb.mjs.map → vue-parser-COgjArqv.mjs.map} +1 -1
  134. package/package.json +75 -21
  135. package/dist/config-B5kc2oeV.mjs +0 -27
  136. package/dist/config-B5kc2oeV.mjs.map +0 -1
  137. package/dist/context-DIDt_TxX.mjs +0 -46
  138. package/dist/context-DIDt_TxX.mjs.map +0 -1
  139. package/dist/core/content-manager.d.mts.map +0 -1
  140. package/dist/core/validator.d.mts +0 -23
  141. package/dist/core/validator.d.mts.map +0 -1
  142. package/dist/core/validator.mjs +0 -7
  143. package/dist/fs-BnIQ4dKj.mjs.map +0 -1
  144. package/dist/model-manager-DW_fOGRM.mjs.map +0 -1
  145. package/dist/scanner-CQOxARUU.mjs.map +0 -1
  146. package/dist/serializer-CnHTtmc1.mjs +0 -2
  147. package/dist/server-DfWRpzdP.mjs.map +0 -1
  148. package/dist/server.d.mts.map +0 -1
  149. package/dist/transaction-C_Pvux78.mjs.map +0 -1
  150. package/dist/tsx-parser-BFLv292Y.mjs +0 -2
  151. package/dist/util/serializer.d.mts +0 -2
  152. package/dist/util/serializer.mjs +0 -2
  153. package/dist/validator-SQYUV2JP.mjs +0 -581
  154. package/dist/validator-SQYUV2JP.mjs.map +0 -1
@@ -1,9 +1,46 @@
1
- import { a as readJson, c as writeText, i as readDir, n as ensureDir, o as readText, s as writeJson, t as contentrainDir } from "./fs-BnIQ4dKj.mjs";
2
- import { r as writeMeta, t as deleteMeta } from "./meta-manager--0IwAPCl.mjs";
3
- import { join } from "node:path";
1
+ import { n as contentFilePath, r as documentFilePath, t as contentDirPath } from "./paths-CYl5vCBD.mjs";
2
+ import { a as readJson, c as writeText, i as readDir, n as ensureDir, o as readText, s as writeJson, t as contentrainDir } from "./fs-DLbVB-Ek.mjs";
3
+ import { r as writeMeta, t as deleteMeta } from "./meta-manager-C900rEyG.mjs";
4
+ import { join, resolve } from "node:path";
5
+ import { generateEntryId, parseMarkdownFrontmatter, parseMarkdownFrontmatter as parseFrontmatter, serializeMarkdownFrontmatter, serializeMarkdownFrontmatter as serializeFrontmatter, validateEntryId as validateEntryId$1, validateLocale as validateLocale$1, validateSlug as validateSlug$1 } from "@contentrain/types";
6
+ import { access, readFile, readdir, rm } from "node:fs/promises";
4
7
  import { z } from "zod";
5
- import { rm } from "node:fs/promises";
6
- import { generateEntryId, parseMarkdownFrontmatter, parseMarkdownFrontmatter as parseFrontmatter, serializeMarkdownFrontmatter, serializeMarkdownFrontmatter as serializeFrontmatter, validateEntryId, validateLocale, validateSlug } from "@contentrain/types";
8
+ //#region src/providers/local/reader.ts
9
+ /**
10
+ * LocalReader — `RepoReader` backed by the local filesystem.
11
+ *
12
+ * Paths may be absolute or relative to `projectRoot` (`node:path/resolve`
13
+ * handles both). The `ref` parameter is accepted for interface compatibility
14
+ * but ignored because LocalReader always reads from the working tree.
15
+ *
16
+ * Phase 1: provided as plumbing; core ops still use direct `fs` calls.
17
+ * Phase 2 routes ops through this reader (and through GitHubProvider's reader
18
+ * in Phase 5) so the same op surface works on every backing store.
19
+ */
20
+ var LocalReader = class {
21
+ constructor(projectRoot) {
22
+ this.projectRoot = projectRoot;
23
+ }
24
+ async readFile(path, _ref) {
25
+ return readFile(resolve(this.projectRoot, path), "utf-8");
26
+ }
27
+ async listDirectory(path, _ref) {
28
+ try {
29
+ return await readdir(resolve(this.projectRoot, path));
30
+ } catch {
31
+ return [];
32
+ }
33
+ }
34
+ async fileExists(path, _ref) {
35
+ try {
36
+ await access(resolve(this.projectRoot, path));
37
+ return true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+ };
43
+ //#endregion
7
44
  //#region src/core/content-manager.ts
8
45
  function resolveContentDir(projectRoot, model) {
9
46
  if (model.content_path) return join(projectRoot, model.content_path);
@@ -42,19 +79,19 @@ function defaultMeta(data) {
42
79
  if (data?.["expire_at"] !== void 0) meta.expire_at = data["expire_at"];
43
80
  return meta;
44
81
  }
45
- async function writeContent(projectRoot, model, entries, config) {
82
+ async function writeContent(projectRoot, model, entries, config, vocabulary) {
46
83
  const results = [];
47
84
  const defaultLocale = config.locales.default;
48
85
  for (const entry of entries) {
49
86
  const locale = entry.locale ?? defaultLocale;
50
- const localeErr = validateLocale(locale, config);
87
+ const localeErr = validateLocale$1(locale, config);
51
88
  if (localeErr) throw new Error(localeErr);
52
89
  if (entry.id) {
53
- const idErr = validateEntryId(entry.id);
90
+ const idErr = validateEntryId$1(entry.id);
54
91
  if (idErr) throw new Error(idErr);
55
92
  }
56
93
  if (entry.slug) {
57
- const slugErr = validateSlug(entry.slug);
94
+ const slugErr = validateSlug$1(entry.slug);
58
95
  if (slugErr) throw new Error(slugErr);
59
96
  }
60
97
  switch (model.kind) {
@@ -90,7 +127,7 @@ async function writeContent(projectRoot, model, entries, config) {
90
127
  case "document": {
91
128
  const slug = entry.slug ?? entry.data["slug"];
92
129
  if (!slug) throw new Error("Document entries require a slug");
93
- const slugErr = validateSlug(slug);
130
+ const slugErr = validateSlug$1(slug);
94
131
  if (slugErr) throw new Error(slugErr);
95
132
  const bodyContent = entry.data["body"] ?? "";
96
133
  const fmData = { ...entry.data };
@@ -116,6 +153,21 @@ async function writeContent(projectRoot, model, entries, config) {
116
153
  const collisions = [];
117
154
  for (const key of Object.keys(entry.data)) if (key in existing && existing[key] !== entry.data[key]) collisions.push(key);
118
155
  if (collisions.length > 0) throw new Error(`Dictionary "${model.id}" (${locale}): ${collisions.length} key collision(s) — [${collisions.join(", ")}] already exist with different values. Read existing keys with contentrain_content_list first, or include all keys in a single save call.`);
156
+ const advisories = [];
157
+ const reverseMap = /* @__PURE__ */ new Map();
158
+ for (const [k, v] of Object.entries(existing)) reverseMap.set(v, k);
159
+ for (const [newKey, newValue] of Object.entries(entry.data)) {
160
+ if (newKey in existing) continue;
161
+ const existingKey = reverseMap.get(newValue);
162
+ if (existingKey && existingKey !== newKey) advisories.push(`Value "${newValue}" already exists as key "${existingKey}". Consider reusing instead of creating "${newKey}".`);
163
+ }
164
+ if (vocabulary && Object.keys(vocabulary.terms).length > 0) for (const [newKey, newValue] of Object.entries(entry.data)) {
165
+ if (newKey in existing) continue;
166
+ for (const [, translations] of Object.entries(vocabulary.terms)) if (Object.values(translations).includes(newValue)) {
167
+ advisories.push(`Value "${newValue}" matches a vocabulary term. Use the canonical form for consistency.`);
168
+ break;
169
+ }
170
+ }
119
171
  await writeJson(filePath, {
120
172
  ...existing,
121
173
  ...entry.data
@@ -123,7 +175,8 @@ async function writeContent(projectRoot, model, entries, config) {
123
175
  await writeMeta(projectRoot, model, { locale }, defaultMeta(entry.data));
124
176
  results.push({
125
177
  action: "updated",
126
- locale
178
+ locale,
179
+ ...advisories.length > 0 ? { advisories } : {}
127
180
  });
128
181
  break;
129
182
  }
@@ -155,7 +208,7 @@ async function deleteContent(projectRoot, model, opts) {
155
208
  }
156
209
  case "document": {
157
210
  if (!opts.slug) throw new Error("Document delete requires a slug");
158
- const slugDelErr = validateSlug(opts.slug);
211
+ const slugDelErr = validateSlug$1(opts.slug);
159
212
  if (slugDelErr) throw new Error(slugDelErr);
160
213
  const strategy = resolveLocaleStrategy(model);
161
214
  if (!model.i18n) await rm(join(cDir, `${opts.slug}.md`), { force: true });
@@ -209,7 +262,11 @@ async function deleteContent(projectRoot, model, opts) {
209
262
  }
210
263
  return removed;
211
264
  }
212
- async function listContent(projectRoot, model, opts, config) {
265
+ async function listContent(input, model, opts, config) {
266
+ if (typeof input !== "string") return listContentViaReader(input, model, opts, config);
267
+ return listContentLocal(input, model, opts, config);
268
+ }
269
+ async function listContentLocal(projectRoot, model, opts, config) {
213
270
  const cDir = resolveContentDir(projectRoot, model);
214
271
  const locale = opts.locale ?? config.locales.default;
215
272
  switch (model.kind) {
@@ -340,6 +397,122 @@ async function listContent(projectRoot, model, opts, config) {
340
397
  }
341
398
  }
342
399
  }
400
+ async function tryReadJsonViaReader$1(reader, path) {
401
+ try {
402
+ return JSON.parse(await reader.readFile(path));
403
+ } catch {
404
+ return null;
405
+ }
406
+ }
407
+ async function tryReadTextViaReader(reader, path) {
408
+ try {
409
+ return await reader.readFile(path);
410
+ } catch {
411
+ return null;
412
+ }
413
+ }
414
+ async function listContentViaReader(reader, model, opts, config) {
415
+ if (opts.resolve) throw new Error("contentrain_content_list with resolve:true requires local filesystem access. Use a LocalProvider (stdio or HTTP+LocalProvider) or omit resolve:true.");
416
+ const cDir = contentDirPath(model);
417
+ const locale = opts.locale ?? config.locales.default;
418
+ switch (model.kind) {
419
+ case "singleton": return {
420
+ kind: "singleton",
421
+ data: await tryReadJsonViaReader$1(reader, contentFilePath(model, locale)) ?? {},
422
+ locale
423
+ };
424
+ case "collection": {
425
+ const data = await tryReadJsonViaReader$1(reader, contentFilePath(model, locale)) ?? {};
426
+ let entries = Object.entries(data).map(([id, fields]) => {
427
+ const entry = { id };
428
+ Object.assign(entry, fields);
429
+ return entry;
430
+ });
431
+ if (opts.filter) entries = entries.filter((entry) => {
432
+ for (const [key, value] of Object.entries(opts.filter)) if (entry[key] !== value) return false;
433
+ return true;
434
+ });
435
+ const total = entries.length;
436
+ const offset = opts.offset ?? 0;
437
+ const limit = opts.limit ?? entries.length;
438
+ entries = entries.slice(offset, offset + limit);
439
+ return {
440
+ kind: "collection",
441
+ data: entries,
442
+ total,
443
+ locale,
444
+ offset,
445
+ limit
446
+ };
447
+ }
448
+ case "document": {
449
+ const entries = [];
450
+ const strategy = resolveLocaleStrategy(model);
451
+ const collectEntry = async (relPath, slug) => {
452
+ const raw = await tryReadTextViaReader(reader, relPath);
453
+ if (!raw) return;
454
+ const { frontmatter, body } = parseMarkdownFrontmatter(raw);
455
+ entries.push({
456
+ slug,
457
+ frontmatter,
458
+ body
459
+ });
460
+ };
461
+ if (!model.i18n) {
462
+ const files = await reader.listDirectory(cDir);
463
+ for (const f of files) {
464
+ if (!f.endsWith(".md")) continue;
465
+ await collectEntry(documentFilePath(model, locale, f.replace(/\.md$/u, "")), f.replace(/\.md$/u, ""));
466
+ }
467
+ } else if (strategy === "file") {
468
+ const slugDirs = await reader.listDirectory(cDir);
469
+ for (const slug of slugDirs) await collectEntry(documentFilePath(model, locale, slug), slug);
470
+ } else if (strategy === "suffix") {
471
+ const files = await reader.listDirectory(cDir);
472
+ const suffix = `.${locale}.md`;
473
+ for (const f of files) {
474
+ if (!f.endsWith(suffix)) continue;
475
+ const slug = f.slice(0, -suffix.length);
476
+ await collectEntry(documentFilePath(model, locale, slug), slug);
477
+ }
478
+ } else if (strategy === "directory") {
479
+ const files = await reader.listDirectory(`${cDir}/${locale}`);
480
+ for (const f of files) {
481
+ if (!f.endsWith(".md")) continue;
482
+ const slug = f.replace(/\.md$/u, "");
483
+ await collectEntry(documentFilePath(model, locale, slug), slug);
484
+ }
485
+ } else {
486
+ const files = await reader.listDirectory(cDir);
487
+ for (const f of files) {
488
+ if (!f.endsWith(".md")) continue;
489
+ const slug = f.replace(/\.md$/u, "");
490
+ await collectEntry(documentFilePath(model, locale, slug), slug);
491
+ }
492
+ }
493
+ const total = entries.length;
494
+ const offset = opts.offset ?? 0;
495
+ const limit = opts.limit ?? entries.length;
496
+ return {
497
+ kind: "document",
498
+ data: entries.slice(offset, offset + limit),
499
+ total,
500
+ locale,
501
+ offset,
502
+ limit
503
+ };
504
+ }
505
+ case "dictionary": {
506
+ const data = await tryReadJsonViaReader$1(reader, contentFilePath(model, locale)) ?? {};
507
+ return {
508
+ kind: "dictionary",
509
+ data,
510
+ total_keys: Object.keys(data).length,
511
+ locale
512
+ };
513
+ }
514
+ }
515
+ }
343
516
  async function readContent(projectRoot, model, opts) {
344
517
  const cDir = resolveContentDir(projectRoot, model);
345
518
  switch (model.kind) {
@@ -495,10 +668,27 @@ async function resolveRelations(projectRoot, model, entries, locale) {
495
668
  }
496
669
  //#endregion
497
670
  //#region src/core/model-manager.ts
498
- async function listModels(projectRoot) {
499
- const modelsDir = join(contentrainDir(projectRoot), "models");
500
- const jsonFiles = (await readDir(modelsDir)).filter((f) => f.endsWith(".json"));
501
- return (await Promise.all(jsonFiles.map((file) => readJson(join(modelsDir, file))))).filter((m) => m !== null && !!m.id).map((model) => ({
671
+ const MODELS_DIR_PATH = ".contentrain/models";
672
+ async function tryReadJsonViaReader(reader, path) {
673
+ try {
674
+ return JSON.parse(await reader.readFile(path));
675
+ } catch {
676
+ return null;
677
+ }
678
+ }
679
+ async function listModels(input) {
680
+ let files;
681
+ let load;
682
+ if (typeof input === "string") {
683
+ const modelsDir = join(contentrainDir(input), "models");
684
+ files = await readDir(modelsDir);
685
+ load = (file) => readJson(join(modelsDir, file));
686
+ } else {
687
+ files = await input.listDirectory(MODELS_DIR_PATH);
688
+ load = (file) => tryReadJsonViaReader(input, `${MODELS_DIR_PATH}/${file}`);
689
+ }
690
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
691
+ return (await Promise.all(jsonFiles.map(load))).filter((m) => m !== null && !!m.id).map((model) => ({
502
692
  id: model.id,
503
693
  kind: model.kind,
504
694
  domain: model.domain,
@@ -506,14 +696,15 @@ async function listModels(projectRoot) {
506
696
  fields: model.fields ? Object.keys(model.fields).length : 0
507
697
  })).toSorted((a, b) => a.id.localeCompare(b.id, "en"));
508
698
  }
509
- async function readModel(projectRoot, modelId) {
510
- return readJson(join(contentrainDir(projectRoot), "models", `${modelId}.json`));
699
+ async function readModel(input, modelId) {
700
+ if (typeof input === "string") return readJson(join(contentrainDir(input), "models", `${modelId}.json`));
701
+ return tryReadJsonViaReader(input, `${MODELS_DIR_PATH}/${modelId}.json`);
511
702
  }
512
- async function countDocumentFileStrategy(contentDir, entries) {
703
+ async function countDocumentFileStrategy(reader, contentDir, entries) {
513
704
  const locales = {};
514
705
  let total = 0;
515
706
  const results = await Promise.all(entries.map(async (entry) => {
516
- const localeFiles = await readDir(join(contentDir, entry));
707
+ const localeFiles = await reader.listDirectory(`${contentDir}/${entry}`);
517
708
  return localeFiles.map((lf) => lf.replace(/\.(json|md|mdx)$/, "")).filter((locale, i) => locale !== localeFiles[i]);
518
709
  }));
519
710
  for (const entryLocales of results) for (const locale of entryLocales) {
@@ -525,7 +716,7 @@ async function countDocumentFileStrategy(contentDir, entries) {
525
716
  locales
526
717
  };
527
718
  }
528
- async function countDocumentSuffixStrategy(_contentDir, files) {
719
+ async function countDocumentSuffixStrategy(_reader, _contentDir, files) {
529
720
  const locales = {};
530
721
  const slugsByLocale = {};
531
722
  for (const f of files) {
@@ -546,13 +737,13 @@ async function countDocumentSuffixStrategy(_contentDir, files) {
546
737
  locales
547
738
  };
548
739
  }
549
- async function countDocumentDirectoryStrategy(contentDir, localeDirs) {
740
+ async function countDocumentDirectoryStrategy(reader, contentDir, localeDirs) {
550
741
  const locales = {};
551
742
  let total = 0;
552
743
  const results = await Promise.all(localeDirs.map(async (localeDir) => {
553
744
  return {
554
745
  locale: localeDir,
555
- count: (await readDir(join(contentDir, localeDir))).filter((f) => f.endsWith(".md")).length
746
+ count: (await reader.listDirectory(`${contentDir}/${localeDir}`)).filter((f) => f.endsWith(".md")).length
556
747
  };
557
748
  }));
558
749
  for (const { locale, count } of results) {
@@ -564,18 +755,18 @@ async function countDocumentDirectoryStrategy(contentDir, localeDirs) {
564
755
  locales
565
756
  };
566
757
  }
567
- async function countDocumentNoneStrategy(projectRoot, modelId, files, i18n) {
758
+ async function countDocumentNoneStrategy(reader, modelId, files, i18n) {
568
759
  const mdFiles = files.filter((f) => f.endsWith(".md"));
569
760
  if (!i18n) return {
570
761
  total: mdFiles.length,
571
762
  locales: { _: mdFiles.length }
572
763
  };
573
- const metaDir = join(contentrainDir(projectRoot), "meta", modelId);
764
+ const metaDir = `.contentrain/meta/${modelId}`;
574
765
  const locales = {};
575
766
  let total = 0;
576
767
  const slugs = mdFiles.map((f) => f.replace(".md", ""));
577
768
  const results = await Promise.all(slugs.map(async (slug) => {
578
- return (await readDir(join(metaDir, slug))).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
769
+ return (await reader.listDirectory(`${metaDir}/${slug}`)).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
579
770
  }));
580
771
  for (const slugLocales of results) for (const locale of slugLocales) {
581
772
  locales[locale] = (locales[locale] ?? 0) + 1;
@@ -586,12 +777,12 @@ async function countDocumentNoneStrategy(projectRoot, modelId, files, i18n) {
586
777
  locales
587
778
  };
588
779
  }
589
- async function countCollectionEntries(contentDir, jsonFiles) {
780
+ async function countCollectionEntries(reader, contentDir, jsonFiles) {
590
781
  const locales = {};
591
782
  let total = 0;
592
783
  const results = await Promise.all(jsonFiles.map(async (file) => {
593
784
  const locale = file.replace(/\.json$/, "");
594
- const data = await readJson(join(contentDir, file));
785
+ const data = await tryReadJsonViaReader(reader, `${contentDir}/${file}`);
595
786
  return {
596
787
  locale,
597
788
  count: data ? Object.keys(data).length : 0
@@ -606,21 +797,22 @@ async function countCollectionEntries(contentDir, jsonFiles) {
606
797
  locales
607
798
  };
608
799
  }
609
- async function countEntries(projectRoot, model) {
610
- const cDir = resolveContentDir(projectRoot, model);
800
+ async function countEntries(input, model) {
801
+ const reader = typeof input === "string" ? new LocalReader(input) : input;
802
+ const cDir = contentDirPath(model);
611
803
  const strategy = resolveLocaleStrategy(model);
612
- const files = await readDir(cDir);
804
+ const files = await reader.listDirectory(cDir);
613
805
  if (model.kind === "document") {
614
- if (!model.i18n) return countDocumentNoneStrategy(projectRoot, model.id, files, false);
806
+ if (!model.i18n) return countDocumentNoneStrategy(reader, model.id, files, false);
615
807
  switch (strategy) {
616
- case "file": return countDocumentFileStrategy(cDir, files);
617
- case "suffix": return countDocumentSuffixStrategy(cDir, files);
618
- case "directory": return countDocumentDirectoryStrategy(cDir, files);
619
- case "none": return countDocumentNoneStrategy(projectRoot, model.id, files, true);
808
+ case "file": return countDocumentFileStrategy(reader, cDir, files);
809
+ case "suffix": return countDocumentSuffixStrategy(reader, cDir, files);
810
+ case "directory": return countDocumentDirectoryStrategy(reader, cDir, files);
811
+ case "none": return countDocumentNoneStrategy(reader, model.id, files, true);
620
812
  }
621
813
  }
622
814
  if (model.kind === "collection") {
623
- if (!model.i18n) return countCollectionEntries(cDir, files.filter((f) => f.endsWith(".json")));
815
+ if (!model.i18n) return countCollectionEntries(reader, cDir, files.filter((f) => f.endsWith(".json")));
624
816
  switch (strategy) {
625
817
  case "suffix": {
626
818
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
@@ -628,7 +820,7 @@ async function countEntries(projectRoot, model) {
628
820
  for (const f of jsonFiles) {
629
821
  const match = f.match(/^.+\.([a-z]{2}(?:-[A-Z]{2})?)\.json$/);
630
822
  if (match) {
631
- const data = await readJson(join(cDir, f));
823
+ const data = await tryReadJsonViaReader(reader, `${cDir}/${f}`);
632
824
  locales[match[1]] = data ? Object.keys(data).length : 0;
633
825
  }
634
826
  }
@@ -641,9 +833,9 @@ async function countEntries(projectRoot, model) {
641
833
  const locales = {};
642
834
  let total = 0;
643
835
  for (const localeDir of files) {
644
- const jsonFile = (await readDir(join(cDir, localeDir))).find((f) => f.endsWith(".json"));
836
+ const jsonFile = (await reader.listDirectory(`${cDir}/${localeDir}`)).find((f) => f.endsWith(".json"));
645
837
  if (jsonFile) {
646
- const data = await readJson(join(cDir, localeDir, jsonFile));
838
+ const data = await tryReadJsonViaReader(reader, `${cDir}/${localeDir}/${jsonFile}`);
647
839
  const count = data ? Object.keys(data).length : 0;
648
840
  locales[localeDir] = count;
649
841
  total += count;
@@ -660,14 +852,14 @@ async function countEntries(projectRoot, model) {
660
852
  total: 0,
661
853
  locales: {}
662
854
  };
663
- const data = await readJson(join(cDir, noneFile));
855
+ const data = await tryReadJsonViaReader(reader, `${cDir}/${noneFile}`);
664
856
  const count = data ? Object.keys(data).length : 0;
665
857
  return {
666
858
  total: count,
667
859
  locales: { _: count }
668
860
  };
669
861
  }
670
- default: return countCollectionEntries(cDir, files.filter((f) => f.endsWith(".json")));
862
+ default: return countCollectionEntries(reader, cDir, files.filter((f) => f.endsWith(".json")));
671
863
  }
672
864
  }
673
865
  if (!model.i18n) return {
@@ -688,7 +880,7 @@ async function countEntries(projectRoot, model) {
688
880
  }
689
881
  case "directory": {
690
882
  const locales = {};
691
- for (const localeDir of files) if ((await readDir(join(cDir, localeDir))).some((f) => f.endsWith(".json"))) locales[localeDir] = 1;
883
+ for (const localeDir of files) if ((await reader.listDirectory(`${cDir}/${localeDir}`)).some((f) => f.endsWith(".json"))) locales[localeDir] = 1;
692
884
  return {
693
885
  total: Object.keys(locales).length,
694
886
  locales
@@ -751,9 +943,9 @@ async function deleteModel(projectRoot, modelId) {
751
943
  } catch {}
752
944
  return removed;
753
945
  }
754
- async function checkReferences(projectRoot, modelId) {
755
- const others = (await listModels(projectRoot)).filter((s) => s.id !== modelId);
756
- const models = await Promise.all(others.map((s) => readModel(projectRoot, s.id)));
946
+ async function checkReferences(input, modelId) {
947
+ const others = (typeof input === "string" ? await listModels(input) : await listModels(input)).filter((s) => s.id !== modelId);
948
+ const models = await Promise.all(others.map((s) => typeof input === "string" ? readModel(input, s.id) : readModel(input, s.id)));
757
949
  const refs = [];
758
950
  for (const model of models) {
759
951
  if (!model?.fields) continue;
@@ -841,6 +1033,6 @@ function validateModelDefinition(input) {
841
1033
  return errors;
842
1034
  }
843
1035
  //#endregion
844
- export { writeContent as S, resolveMdFilePath as _, fieldDefZodSchema as a, validateLocale as b, validateModelDefinition as c, listContent as d, parseFrontmatter as f, resolveLocaleStrategy as g, resolveJsonFilePath as h, deleteModel as i, writeModel as l, resolveContentDir as m, checkReferences as n, listModels as o, readContent as p, countEntries as r, readModel as s, FIELD_TYPE_ENUM as t, deleteContent as u, serializeFrontmatter as v, validateSlug as x, validateEntryId as y };
1036
+ export { LocalReader as C, writeContent as S, resolveMdFilePath as _, fieldDefZodSchema as a, validateLocale$1 as b, validateModelDefinition as c, listContent as d, parseFrontmatter as f, resolveLocaleStrategy as g, resolveJsonFilePath as h, deleteModel as i, writeModel as l, resolveContentDir as m, checkReferences as n, listModels as o, readContent as p, countEntries as r, readModel as s, FIELD_TYPE_ENUM as t, deleteContent as u, serializeFrontmatter as v, validateSlug$1 as x, validateEntryId$1 as y };
845
1037
 
846
- //# sourceMappingURL=model-manager-DW_fOGRM.mjs.map
1038
+ //# sourceMappingURL=model-manager-BhLsUgaB.mjs.map