@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.
- package/README.md +93 -25
- package/dist/annotations-Clhbw2lh.mjs +126 -0
- package/dist/annotations-Clhbw2lh.mjs.map +1 -0
- package/dist/{apply-manager-BkPMfpsN.mjs → apply-manager-D5ym5Jba.mjs} +7 -7
- package/dist/{apply-manager-BkPMfpsN.mjs.map → apply-manager-D5ym5Jba.mjs.map} +1 -1
- package/dist/{astro-parser-BJ-54JGe.mjs → astro-parser-B-nLl0zE.mjs} +3 -3
- package/dist/{astro-parser-BJ-54JGe.mjs.map → astro-parser-B-nLl0zE.mjs.map} +1 -1
- package/dist/{branch-lifecycle-wGJg25LT.mjs → branch-lifecycle-CFVpTpNT.mjs} +33 -3
- package/dist/{branch-lifecycle-wGJg25LT.mjs.map → branch-lifecycle-CFVpTpNT.mjs.map} +1 -1
- package/dist/{compiler-rIC4ETHv.mjs → compiler-k4yttlzM.mjs} +2 -2
- package/dist/{compiler-rIC4ETHv.mjs.map → compiler-k4yttlzM.mjs.map} +1 -1
- package/dist/{compiler-sfc.cjs-BbD04rn_.mjs → compiler-sfc.cjs-D0d_OicF.mjs} +2 -2
- package/dist/{compiler-sfc.cjs-BbD04rn_.mjs.map → compiler-sfc.cjs-D0d_OicF.mjs.map} +1 -1
- package/dist/config-BIl5Ybj0.mjs +40 -0
- package/dist/config-BIl5Ybj0.mjs.map +1 -0
- package/dist/content-manager-DqDLSNQ-.d.mts +63 -0
- package/dist/content-manager-DqDLSNQ-.d.mts.map +1 -0
- package/dist/context-J0OU9Nfp.mjs +102 -0
- package/dist/context-J0OU9Nfp.mjs.map +1 -0
- package/dist/contracts-DfL0BfrD.mjs +2 -0
- package/dist/core/apply-manager.mjs +10 -10
- package/dist/core/config.d.mts +13 -0
- package/dist/core/config.d.mts.map +1 -1
- package/dist/core/config.mjs +2 -3
- package/dist/core/content-manager.d.mts +2 -45
- package/dist/core/content-manager.mjs +3 -4
- package/dist/core/context.d.mts +32 -2
- package/dist/core/context.d.mts.map +1 -1
- package/dist/core/context.mjs +7 -7
- package/dist/core/contracts/index.d.mts +2 -0
- package/dist/core/contracts/index.mjs +2 -0
- package/dist/core/doctor.d.mts +76 -0
- package/dist/core/doctor.d.mts.map +1 -0
- package/dist/core/doctor.mjs +8 -0
- package/dist/core/graph-builder.mjs +3 -4
- package/dist/core/meta-manager.mjs +2 -3
- package/dist/core/model-manager.d.mts +34 -1
- package/dist/core/model-manager.d.mts.map +1 -1
- package/dist/core/model-manager.mjs +3 -4
- package/dist/core/ops/index.d.mts +137 -0
- package/dist/core/ops/index.d.mts.map +1 -0
- package/dist/core/ops/index.mjs +4 -0
- package/dist/core/overlay-reader.d.mts +42 -0
- package/dist/core/overlay-reader.d.mts.map +1 -0
- package/dist/core/overlay-reader.mjs +2 -0
- package/dist/core/scan-config.mjs +2 -3
- package/dist/core/scanner.mjs +4 -5
- package/dist/core/validator/index.d.mts +129 -0
- package/dist/core/validator/index.d.mts.map +1 -0
- package/dist/core/validator/index.mjs +6 -0
- package/dist/{detect-DinOQAmK.mjs → detect-DL9mFmaS.mjs} +2 -2
- package/dist/{detect-DinOQAmK.mjs.map → detect-DL9mFmaS.mjs.map} +1 -1
- package/dist/doctor-CgBoT6h1.mjs +338 -0
- package/dist/doctor-CgBoT6h1.mjs.map +1 -0
- package/dist/{fs-BnIQ4dKj.mjs → fs-DLbVB-Ek.mjs} +2 -2
- package/dist/fs-DLbVB-Ek.mjs.map +1 -0
- package/dist/git/branch-lifecycle.d.mts +30 -1
- package/dist/git/branch-lifecycle.d.mts.map +1 -1
- package/dist/git/branch-lifecycle.mjs +4 -5
- package/dist/git/transaction.d.mts +2 -2
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +8 -8
- package/dist/{graph-builder-tM7zj-SX.mjs → graph-builder-DXv2JEp-.mjs} +3 -3
- package/dist/{graph-builder-tM7zj-SX.mjs.map → graph-builder-DXv2JEp-.mjs.map} +1 -1
- package/dist/{id-DV_T9Ic8.mjs → id-CIO9s0x_.mjs} +1 -1
- package/dist/{id-DV_T9Ic8.mjs.map → id-CIO9s0x_.mjs.map} +1 -1
- package/dist/index-D0wQJSgs.d.mts +2 -0
- package/dist/index.mjs +22 -17
- package/dist/index.mjs.map +1 -1
- package/dist/local-DvP4Y4T-.mjs +182 -0
- package/dist/local-DvP4Y4T-.mjs.map +1 -0
- package/dist/{meta-manager--0IwAPCl.mjs → meta-manager-C900rEyG.mjs} +2 -2
- package/dist/{meta-manager--0IwAPCl.mjs.map → meta-manager-C900rEyG.mjs.map} +1 -1
- package/dist/{model-manager-DW_fOGRM.mjs → model-manager-BhLsUgaB.mjs} +241 -49
- package/dist/model-manager-BhLsUgaB.mjs.map +1 -0
- package/dist/{node-Cbq5eR5i.mjs → node-DN9hZeil.mjs} +1 -1
- package/dist/{node-Cbq5eR5i.mjs.map → node-DN9hZeil.mjs.map} +1 -1
- package/dist/ops-DjEWi5Wa.mjs +486 -0
- package/dist/ops-DjEWi5Wa.mjs.map +1 -0
- package/dist/overlay-reader-DOdyDRIv.mjs +74 -0
- package/dist/overlay-reader-DOdyDRIv.mjs.map +1 -0
- package/dist/paths-C3xNyc7z.mjs +52 -0
- package/dist/paths-C3xNyc7z.mjs.map +1 -0
- package/dist/paths-CYl5vCBD.mjs +54 -0
- package/dist/paths-CYl5vCBD.mjs.map +1 -0
- package/dist/providers/github/index.d.mts +144 -0
- package/dist/providers/github/index.d.mts.map +1 -0
- package/dist/providers/github/index.mjs +414 -0
- package/dist/providers/github/index.mjs.map +1 -0
- package/dist/providers/gitlab/index.d.mts +158 -0
- package/dist/providers/gitlab/index.d.mts.map +1 -0
- package/dist/providers/gitlab/index.mjs +361 -0
- package/dist/providers/gitlab/index.mjs.map +1 -0
- package/dist/providers/local/index.d.mts +106 -0
- package/dist/providers/local/index.d.mts.map +1 -0
- package/dist/providers/local/index.mjs +12 -0
- package/dist/{scan-config-QgnJ1-52.mjs → scan-config-jC6XmiqF.mjs} +2 -2
- package/dist/{scan-config-QgnJ1-52.mjs.map → scan-config-jC6XmiqF.mjs.map} +1 -1
- package/dist/{scanner-CQOxARUU.mjs → scanner-DKMwc03k.mjs} +26 -8
- package/dist/scanner-DKMwc03k.mjs.map +1 -0
- package/dist/serialization-B1CEzR4H.mjs +2 -0
- package/dist/server/http/index.d.mts +65 -0
- package/dist/server/http/index.d.mts.map +1 -0
- package/dist/server/http/index.mjs +137 -0
- package/dist/server/http/index.mjs.map +1 -0
- package/dist/{server-DfWRpzdP.mjs → server-B1OJrUQ9.mjs} +486 -355
- package/dist/server-B1OJrUQ9.mjs.map +1 -0
- package/dist/server-WqGx6ait.d.mts +50 -0
- package/dist/server-WqGx6ait.d.mts.map +1 -0
- package/dist/server.d.mts +2 -7
- package/dist/server.mjs +22 -17
- package/dist/{source-map-generator-DpO-ejrt.mjs → source-map-generator-BWDva49I.mjs} +1 -1
- package/dist/{source-map-generator-DpO-ejrt.mjs.map → source-map-generator-BWDva49I.mjs.map} +1 -1
- package/dist/{svelte-parser-1bBcMx3T.mjs → svelte-parser-Dr9mmZuk.mjs} +3 -3
- package/dist/{svelte-parser-1bBcMx3T.mjs.map → svelte-parser-Dr9mmZuk.mjs.map} +1 -1
- package/dist/templates/index.mjs +1 -1
- package/dist/{templates-bRPzYq4M.mjs → templates-VrLVqfe5.mjs} +1 -1
- package/dist/{templates-bRPzYq4M.mjs.map → templates-VrLVqfe5.mjs.map} +1 -1
- package/dist/tools/annotations.d.mts +24 -0
- package/dist/tools/annotations.d.mts.map +1 -0
- package/dist/tools/annotations.mjs +2 -0
- package/dist/{transaction-C_Pvux78.mjs → transaction-DgkNgWSI.mjs} +61 -32
- package/dist/transaction-DgkNgWSI.mjs.map +1 -0
- package/dist/tsx-parser-BOMZE_yQ.mjs +2 -0
- package/dist/{tsx-parser-BWiMGAIR.mjs → tsx-parser-CQc6nr-o.mjs} +1 -1
- package/dist/{tsx-parser-BWiMGAIR.mjs.map → tsx-parser-CQc6nr-o.mjs.map} +1 -1
- package/dist/util/detect.mjs +2 -3
- package/dist/util/fs.mjs +1 -2
- package/dist/util/id.mjs +1 -1
- package/dist/validator-B0nNRFeU.mjs +906 -0
- package/dist/validator-B0nNRFeU.mjs.map +1 -0
- package/dist/{vue-parser-DTPTN3Yb.mjs → vue-parser-COgjArqv.mjs} +3 -3
- package/dist/{vue-parser-DTPTN3Yb.mjs.map → vue-parser-COgjArqv.mjs.map} +1 -1
- package/package.json +75 -21
- package/dist/config-B5kc2oeV.mjs +0 -27
- package/dist/config-B5kc2oeV.mjs.map +0 -1
- package/dist/context-DIDt_TxX.mjs +0 -46
- package/dist/context-DIDt_TxX.mjs.map +0 -1
- package/dist/core/content-manager.d.mts.map +0 -1
- package/dist/core/validator.d.mts +0 -23
- package/dist/core/validator.d.mts.map +0 -1
- package/dist/core/validator.mjs +0 -7
- package/dist/fs-BnIQ4dKj.mjs.map +0 -1
- package/dist/model-manager-DW_fOGRM.mjs.map +0 -1
- package/dist/scanner-CQOxARUU.mjs.map +0 -1
- package/dist/serializer-CnHTtmc1.mjs +0 -2
- package/dist/server-DfWRpzdP.mjs.map +0 -1
- package/dist/server.d.mts.map +0 -1
- package/dist/transaction-C_Pvux78.mjs.map +0 -1
- package/dist/tsx-parser-BFLv292Y.mjs +0 -2
- package/dist/util/serializer.d.mts +0 -2
- package/dist/util/serializer.mjs +0 -2
- package/dist/validator-SQYUV2JP.mjs +0 -581
- package/dist/validator-SQYUV2JP.mjs.map +0 -1
|
@@ -1,9 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
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(
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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(
|
|
510
|
-
return readJson(join(contentrainDir(
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
610
|
-
const
|
|
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
|
|
804
|
+
const files = await reader.listDirectory(cDir);
|
|
613
805
|
if (model.kind === "document") {
|
|
614
|
-
if (!model.i18n) return countDocumentNoneStrategy(
|
|
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(
|
|
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
|
|
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
|
|
836
|
+
const jsonFile = (await reader.listDirectory(`${cDir}/${localeDir}`)).find((f) => f.endsWith(".json"));
|
|
645
837
|
if (jsonFile) {
|
|
646
|
-
const data = await
|
|
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
|
|
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
|
|
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(
|
|
755
|
-
const others = (await listModels(
|
|
756
|
-
const models = await Promise.all(others.map((s) => readModel(
|
|
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-
|
|
1038
|
+
//# sourceMappingURL=model-manager-BhLsUgaB.mjs.map
|