@contentrain/query 7.6.39 → 7.6.41
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 +5 -0
- package/dist/astro/index.cjs +31 -7
- package/dist/astro/index.d.cts +0 -20
- package/dist/astro/index.d.cts.map +1 -1
- package/dist/astro/index.d.mts +0 -20
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +31 -7
- package/dist/astro/index.mjs.map +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{frontmatter-Bf6Y8vAj.mjs → frontmatter-BGQD38mL.mjs} +73 -38
- package/dist/frontmatter-BGQD38mL.mjs.map +1 -0
- package/dist/{frontmatter-CvpQ8lJn.cjs → frontmatter-BRrMvaPa.cjs} +70 -35
- package/dist/{generate-BEfRkd7j.mjs → generate-1RkU4u-9.mjs} +2 -2
- package/dist/{generate-BEfRkd7j.mjs.map → generate-1RkU4u-9.mjs.map} +1 -1
- package/dist/{generate-BwG58cTu.cjs → generate-CaSNN7VE.cjs} +1 -1
- package/dist/generator/generate.cjs +1 -1
- package/dist/generator/generate.mjs +1 -1
- package/dist/publication-C90PRY0Y.d.mts.map +1 -1
- package/dist/publication-CH4OXXzW.d.cts.map +1 -1
- package/package.json +2 -2
- package/dist/frontmatter-Bf6Y8vAj.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -525,6 +525,11 @@ loads with ids prefixed (`en/my-post`) — Astro ids are unique per collection,
|
|
|
525
525
|
without the prefix one language would overwrite the other. `root` points at the
|
|
526
526
|
project holding `.contentrain` (default: `process.cwd()`).
|
|
527
527
|
|
|
528
|
+
The loaders of one Astro sync share a single read of the project, and the loader keeps at most 64 files open at a
|
|
529
|
+
time, so a site with hundreds of models builds under a small file-descriptor limit. A file that is missing reads as
|
|
530
|
+
absent. Any other read failure fails the build instead of dropping content. A content file that is not valid JSON loads
|
|
531
|
+
no entries, and the loader logs a warning naming the model and the file.
|
|
532
|
+
|
|
528
533
|
This loader reads `.contentrain` directly, so nothing upstream has rewritten a
|
|
529
534
|
stored `media/...` reference into an absolute URL yet — pass `mediaBaseUrl` to
|
|
530
535
|
resolve it here, the same opt-in resolver `media()`/`mediaBody()` use:
|
package/dist/astro/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_media = require("../media-o6KezVYy.cjs");
|
|
3
|
-
const require_frontmatter = require("../frontmatter-
|
|
3
|
+
const require_frontmatter = require("../frontmatter-BRrMvaPa.cjs");
|
|
4
4
|
let _contentrain_types = require("@contentrain/types");
|
|
5
5
|
let node_path = require("node:path");
|
|
6
6
|
let node_url = require("node:url");
|
|
@@ -25,13 +25,33 @@ let node_url = require("node:url");
|
|
|
25
25
|
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
26
26
|
* entry, id `<model>` or the locale.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* One manifest per project root for each Astro sync. Astro starts every collection's loader at once and hands them
|
|
30
|
+
* the same config object, so the loaders of one build share a single read of the models and content files instead
|
|
31
|
+
* of each reading them all again. The dev server re-reads on every load (it reloads when a file changes); without a
|
|
32
|
+
* config, as in a test, nothing is shared.
|
|
33
|
+
*/
|
|
34
|
+
const manifests = /* @__PURE__ */ new WeakMap();
|
|
35
|
+
function manifestFor(root, context) {
|
|
36
|
+
const config = context.config;
|
|
37
|
+
if (context.watcher || !config) return require_frontmatter.readProjectManifest(root);
|
|
38
|
+
let byRoot = manifests.get(config);
|
|
39
|
+
if (!byRoot) manifests.set(config, byRoot = /* @__PURE__ */ new Map());
|
|
40
|
+
let manifest = byRoot.get(root);
|
|
41
|
+
if (!manifest) {
|
|
42
|
+
manifest = require_frontmatter.readProjectManifest(root);
|
|
43
|
+
byRoot.set(root, manifest);
|
|
44
|
+
manifest.catch(() => byRoot.delete(root));
|
|
45
|
+
}
|
|
46
|
+
return manifest;
|
|
47
|
+
}
|
|
28
48
|
function contentrainLoader(options) {
|
|
29
49
|
const root = options.root ?? process.cwd();
|
|
30
50
|
let watching = false;
|
|
31
51
|
return {
|
|
32
52
|
name: `contentrain:${options.model}`,
|
|
33
53
|
async load(context) {
|
|
34
|
-
const manifest = await
|
|
54
|
+
const manifest = await manifestFor(root, context);
|
|
35
55
|
const model = manifest.models.find((m) => m.id === options.model);
|
|
36
56
|
if (!model) {
|
|
37
57
|
const known = manifest.models.map((m) => m.id).join(", ") || "none";
|
|
@@ -42,7 +62,10 @@ function contentrainLoader(options) {
|
|
|
42
62
|
const prefixLocale = model.i18n === true && options.locale === void 0;
|
|
43
63
|
const publication = require_frontmatter.publicationContext(root, manifest.config.locales.default, options);
|
|
44
64
|
const perFile = await Promise.all(refs.map((ref) => entriesOf(ref, model, prefixLocale, publication)));
|
|
45
|
-
const
|
|
65
|
+
const unread = refs.filter((_, i) => perFile[i] === null);
|
|
66
|
+
if (unread.length) context.logger?.warn(`contentrainLoader: model "${model.id}": ${refs.length} content file${refs.length === 1 ? "" : "s"} found, ${refs.length - unread.length} read — no entries from ${unread.map((ref) => (0, node_path.relative)(root, ref.filePath)).join(", ")} (not valid JSON).`);
|
|
67
|
+
const loaded = perFile.flatMap((file) => file ?? []);
|
|
68
|
+
const entries = options.mediaBaseUrl ? resolveEntriesMedia(loaded, model, options.mediaBaseUrl) : loaded;
|
|
46
69
|
const siteRoot = toPath(context.config?.root);
|
|
47
70
|
context.store.clear();
|
|
48
71
|
for (const entry of entries) {
|
|
@@ -111,6 +134,7 @@ function resolveEntriesMedia(entries, model, mediaBaseUrl) {
|
|
|
111
134
|
};
|
|
112
135
|
});
|
|
113
136
|
}
|
|
137
|
+
/** The file's entries a build may show; null when the file reads as nothing (not valid JSON, or gone since it was listed). */
|
|
114
138
|
async function entriesOf(ref, model, prefixLocale, publication) {
|
|
115
139
|
const meta = publication ? await require_frontmatter.publicationMeta(ref, model, publication) : void 0;
|
|
116
140
|
const visible = (id) => !publication || require_frontmatter.isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at, publication.requireStatus);
|
|
@@ -123,7 +147,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
123
147
|
switch (ref.kind) {
|
|
124
148
|
case "collection": {
|
|
125
149
|
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
126
|
-
if (!raw) return
|
|
150
|
+
if (!raw) return null;
|
|
127
151
|
return Object.entries(raw).filter(([id]) => visible(id)).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
128
152
|
id: `${prefix}${id}`,
|
|
129
153
|
data: withLocale({
|
|
@@ -135,7 +159,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
135
159
|
}
|
|
136
160
|
case "singleton": {
|
|
137
161
|
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
138
|
-
if (!raw) return
|
|
162
|
+
if (!raw) return null;
|
|
139
163
|
return [{
|
|
140
164
|
id: prefix ? `${ref.locale}` : model.id,
|
|
141
165
|
data: withLocale(raw),
|
|
@@ -144,7 +168,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
144
168
|
}
|
|
145
169
|
case "dictionary": {
|
|
146
170
|
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
147
|
-
if (!raw) return
|
|
171
|
+
if (!raw) return null;
|
|
148
172
|
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
149
173
|
id: `${prefix}${key}`,
|
|
150
174
|
data: withLocale({
|
|
@@ -156,7 +180,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
156
180
|
}
|
|
157
181
|
case "document": {
|
|
158
182
|
const text = await require_frontmatter.readText(ref.filePath);
|
|
159
|
-
if (text === null) return
|
|
183
|
+
if (text === null) return null;
|
|
160
184
|
const { frontmatter, body } = require_frontmatter.parseFrontmatter(text, require_frontmatter.stringLikeFieldKeys(model));
|
|
161
185
|
const slug = ref.slug ?? model.id;
|
|
162
186
|
return [{
|
package/dist/astro/index.d.cts
CHANGED
|
@@ -73,26 +73,6 @@ interface ContentrainLoaderOptions extends PublicationOptions {
|
|
|
73
73
|
*/
|
|
74
74
|
mediaBaseUrl?: string;
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
77
|
-
* A loader for one Contentrain model.
|
|
78
|
-
*
|
|
79
|
-
* ```ts
|
|
80
|
-
* // src/content.config.ts
|
|
81
|
-
* import { defineCollection } from 'astro:content'
|
|
82
|
-
* import { contentrainLoader } from '@contentrain/query/astro'
|
|
83
|
-
*
|
|
84
|
-
* export const collections = {
|
|
85
|
-
* posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),
|
|
86
|
-
* articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
89
|
-
*
|
|
90
|
-
* Each model kind maps to the shape Astro can actually use:
|
|
91
|
-
* collection → one entry per record · document → one entry per file, markdown
|
|
92
|
-
* in `body` (and rendered, so `<Content />` works) · dictionary → one entry per
|
|
93
|
-
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
94
|
-
* entry, id `<model>` or the locale.
|
|
95
|
-
*/
|
|
96
76
|
declare function contentrainLoader(options: ContentrainLoaderOptions): ContentrainLoader;
|
|
97
77
|
//#endregion
|
|
98
78
|
export { type ContentrainLoader, type ContentrainLoaderContext, type ContentrainLoaderOptions, contentrainLoader };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":";;;;;AAgCA;;;;;;UAAiB,wBAAA;EACf,KAAA;IACE,KAAA;IACA,GAAA,GAAM,KAAA;MACJ,EAAA;MACA,IAAA,EAAM,MAAA;MACN,IAAA;MACA,MAAA;MACA,QAAA;QAAa,IAAA;QAAc,QAAA,GAAW,MAAA;MAAA;MACtC,QAAA;IAAA;EAAA;EAGJ,MAAA;IAAW,IAAA,GAAO,GAAA;IAAsB,IAAA,GAAO,GAAA;EAAA;EAJL;EAM1C,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,KAAmB,IAAA,EAAM,MAAA;EACzB,SAAA,KAAc,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EAClG,cAAA,KAAmB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EAC1E,OAAA;IAAY,GAAA,GAAM,IAAA;IAAuB,EAAA,GAAK,KAAA,UAAe,EAAA,GAAK,IAAA;EAAA;AAAA;;UAInD,iBAAA;EACf,IAAA;EACA,IAAA,GAAO,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,UAG9B,wBAAA,SAAiC,kBAAA;EAVC;EAYjD,KAAA;EAZ0E;EAc1E,IAAA;EAbY;;;;;EAmBZ,MAAA;EAnBuF;;AAIzF;;;;;;EAwBE,YAAA;AAAA
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":";;;;;AAgCA;;;;;;UAAiB,wBAAA;EACf,KAAA;IACE,KAAA;IACA,GAAA,GAAM,KAAA;MACJ,EAAA;MACA,IAAA,EAAM,MAAA;MACN,IAAA;MACA,MAAA;MACA,QAAA;QAAa,IAAA;QAAc,QAAA,GAAW,MAAA;MAAA;MACtC,QAAA;IAAA;EAAA;EAGJ,MAAA;IAAW,IAAA,GAAO,GAAA;IAAsB,IAAA,GAAO,GAAA;EAAA;EAJL;EAM1C,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,KAAmB,IAAA,EAAM,MAAA;EACzB,SAAA,KAAc,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EAClG,cAAA,KAAmB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EAC1E,OAAA;IAAY,GAAA,GAAM,IAAA;IAAuB,EAAA,GAAK,KAAA,UAAe,EAAA,GAAK,IAAA;EAAA;AAAA;;UAInD,iBAAA;EACf,IAAA;EACA,IAAA,GAAO,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,UAG9B,wBAAA,SAAiC,kBAAA;EAVC;EAYjD,KAAA;EAZ0E;EAc1E,IAAA;EAbY;;;;;EAmBZ,MAAA;EAnBuF;;AAIzF;;;;;;EAwBE,YAAA;AAAA;AAAA,iBAqDc,iBAAA,CAAkB,OAAA,EAAS,wBAAA,GAA2B,iBAAA"}
|
package/dist/astro/index.d.mts
CHANGED
|
@@ -73,26 +73,6 @@ interface ContentrainLoaderOptions extends PublicationOptions {
|
|
|
73
73
|
*/
|
|
74
74
|
mediaBaseUrl?: string;
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
77
|
-
* A loader for one Contentrain model.
|
|
78
|
-
*
|
|
79
|
-
* ```ts
|
|
80
|
-
* // src/content.config.ts
|
|
81
|
-
* import { defineCollection } from 'astro:content'
|
|
82
|
-
* import { contentrainLoader } from '@contentrain/query/astro'
|
|
83
|
-
*
|
|
84
|
-
* export const collections = {
|
|
85
|
-
* posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),
|
|
86
|
-
* articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
89
|
-
*
|
|
90
|
-
* Each model kind maps to the shape Astro can actually use:
|
|
91
|
-
* collection → one entry per record · document → one entry per file, markdown
|
|
92
|
-
* in `body` (and rendered, so `<Content />` works) · dictionary → one entry per
|
|
93
|
-
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
94
|
-
* entry, id `<model>` or the locale.
|
|
95
|
-
*/
|
|
96
76
|
declare function contentrainLoader(options: ContentrainLoaderOptions): ContentrainLoader;
|
|
97
77
|
//#endregion
|
|
98
78
|
export { type ContentrainLoader, type ContentrainLoaderContext, type ContentrainLoaderOptions, contentrainLoader };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":";;;;;AAgCA;;;;;;UAAiB,wBAAA;EACf,KAAA;IACE,KAAA;IACA,GAAA,GAAM,KAAA;MACJ,EAAA;MACA,IAAA,EAAM,MAAA;MACN,IAAA;MACA,MAAA;MACA,QAAA;QAAa,IAAA;QAAc,QAAA,GAAW,MAAA;MAAA;MACtC,QAAA;IAAA;EAAA;EAGJ,MAAA;IAAW,IAAA,GAAO,GAAA;IAAsB,IAAA,GAAO,GAAA;EAAA;EAJL;EAM1C,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,KAAmB,IAAA,EAAM,MAAA;EACzB,SAAA,KAAc,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EAClG,cAAA,KAAmB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EAC1E,OAAA;IAAY,GAAA,GAAM,IAAA;IAAuB,EAAA,GAAK,KAAA,UAAe,EAAA,GAAK,IAAA;EAAA;AAAA;;UAInD,iBAAA;EACf,IAAA;EACA,IAAA,GAAO,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,UAG9B,wBAAA,SAAiC,kBAAA;EAVC;EAYjD,KAAA;EAZ0E;EAc1E,IAAA;EAbY;;;;;EAmBZ,MAAA;EAnBuF;;AAIzF;;;;;;EAwBE,YAAA;AAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":";;;;;AAgCA;;;;;;UAAiB,wBAAA;EACf,KAAA;IACE,KAAA;IACA,GAAA,GAAM,KAAA;MACJ,EAAA;MACA,IAAA,EAAM,MAAA;MACN,IAAA;MACA,MAAA;MACA,QAAA;QAAa,IAAA;QAAc,QAAA,GAAW,MAAA;MAAA;MACtC,QAAA;IAAA;EAAA;EAGJ,MAAA;IAAW,IAAA,GAAO,GAAA;IAAsB,IAAA,GAAO,GAAA;EAAA;EAJL;EAM1C,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,KAAmB,IAAA,EAAM,MAAA;EACzB,SAAA,KAAc,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EAClG,cAAA,KAAmB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EAC1E,OAAA;IAAY,GAAA,GAAM,IAAA;IAAuB,EAAA,GAAK,KAAA,UAAe,EAAA,GAAK,IAAA;EAAA;AAAA;;UAInD,iBAAA;EACf,IAAA;EACA,IAAA,GAAO,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,UAG9B,wBAAA,SAAiC,kBAAA;EAVC;EAYjD,KAAA;EAZ0E;EAc1E,IAAA;EAbY;;;;;EAmBZ,MAAA;EAnBuF;;AAIzF;;;;;;EAwBE,YAAA;AAAA;AAAA,iBAqDc,iBAAA,CAAkB,OAAA,EAAS,wBAAA,GAA2B,iBAAA"}
|
package/dist/astro/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as resolveMediaRefsInBody } from "../media-HEEDdrFS.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as publicationContext, c as readJson, i as isPublishedAt, l as readText, n as stringLikeFieldKeys, o as publicationMeta, r as readProjectManifest, t as parseFrontmatter } from "../frontmatter-BGQD38mL.mjs";
|
|
3
3
|
import { rewriteEntryMedia } from "@contentrain/types";
|
|
4
4
|
import { isAbsolute, join, relative } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -24,13 +24,33 @@ import { fileURLToPath } from "node:url";
|
|
|
24
24
|
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
25
25
|
* entry, id `<model>` or the locale.
|
|
26
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* One manifest per project root for each Astro sync. Astro starts every collection's loader at once and hands them
|
|
29
|
+
* the same config object, so the loaders of one build share a single read of the models and content files instead
|
|
30
|
+
* of each reading them all again. The dev server re-reads on every load (it reloads when a file changes); without a
|
|
31
|
+
* config, as in a test, nothing is shared.
|
|
32
|
+
*/
|
|
33
|
+
const manifests = /* @__PURE__ */ new WeakMap();
|
|
34
|
+
function manifestFor(root, context) {
|
|
35
|
+
const config = context.config;
|
|
36
|
+
if (context.watcher || !config) return readProjectManifest(root);
|
|
37
|
+
let byRoot = manifests.get(config);
|
|
38
|
+
if (!byRoot) manifests.set(config, byRoot = /* @__PURE__ */ new Map());
|
|
39
|
+
let manifest = byRoot.get(root);
|
|
40
|
+
if (!manifest) {
|
|
41
|
+
manifest = readProjectManifest(root);
|
|
42
|
+
byRoot.set(root, manifest);
|
|
43
|
+
manifest.catch(() => byRoot.delete(root));
|
|
44
|
+
}
|
|
45
|
+
return manifest;
|
|
46
|
+
}
|
|
27
47
|
function contentrainLoader(options) {
|
|
28
48
|
const root = options.root ?? process.cwd();
|
|
29
49
|
let watching = false;
|
|
30
50
|
return {
|
|
31
51
|
name: `contentrain:${options.model}`,
|
|
32
52
|
async load(context) {
|
|
33
|
-
const manifest = await
|
|
53
|
+
const manifest = await manifestFor(root, context);
|
|
34
54
|
const model = manifest.models.find((m) => m.id === options.model);
|
|
35
55
|
if (!model) {
|
|
36
56
|
const known = manifest.models.map((m) => m.id).join(", ") || "none";
|
|
@@ -41,7 +61,10 @@ function contentrainLoader(options) {
|
|
|
41
61
|
const prefixLocale = model.i18n === true && options.locale === void 0;
|
|
42
62
|
const publication = publicationContext(root, manifest.config.locales.default, options);
|
|
43
63
|
const perFile = await Promise.all(refs.map((ref) => entriesOf(ref, model, prefixLocale, publication)));
|
|
44
|
-
const
|
|
64
|
+
const unread = refs.filter((_, i) => perFile[i] === null);
|
|
65
|
+
if (unread.length) context.logger?.warn(`contentrainLoader: model "${model.id}": ${refs.length} content file${refs.length === 1 ? "" : "s"} found, ${refs.length - unread.length} read — no entries from ${unread.map((ref) => relative(root, ref.filePath)).join(", ")} (not valid JSON).`);
|
|
66
|
+
const loaded = perFile.flatMap((file) => file ?? []);
|
|
67
|
+
const entries = options.mediaBaseUrl ? resolveEntriesMedia(loaded, model, options.mediaBaseUrl) : loaded;
|
|
45
68
|
const siteRoot = toPath(context.config?.root);
|
|
46
69
|
context.store.clear();
|
|
47
70
|
for (const entry of entries) {
|
|
@@ -110,6 +133,7 @@ function resolveEntriesMedia(entries, model, mediaBaseUrl) {
|
|
|
110
133
|
};
|
|
111
134
|
});
|
|
112
135
|
}
|
|
136
|
+
/** The file's entries a build may show; null when the file reads as nothing (not valid JSON, or gone since it was listed). */
|
|
113
137
|
async function entriesOf(ref, model, prefixLocale, publication) {
|
|
114
138
|
const meta = publication ? await publicationMeta(ref, model, publication) : void 0;
|
|
115
139
|
const visible = (id) => !publication || isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at, publication.requireStatus);
|
|
@@ -122,7 +146,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
122
146
|
switch (ref.kind) {
|
|
123
147
|
case "collection": {
|
|
124
148
|
const raw = await readJson(ref.filePath);
|
|
125
|
-
if (!raw) return
|
|
149
|
+
if (!raw) return null;
|
|
126
150
|
return Object.entries(raw).filter(([id]) => visible(id)).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
127
151
|
id: `${prefix}${id}`,
|
|
128
152
|
data: withLocale({
|
|
@@ -134,7 +158,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
134
158
|
}
|
|
135
159
|
case "singleton": {
|
|
136
160
|
const raw = await readJson(ref.filePath);
|
|
137
|
-
if (!raw) return
|
|
161
|
+
if (!raw) return null;
|
|
138
162
|
return [{
|
|
139
163
|
id: prefix ? `${ref.locale}` : model.id,
|
|
140
164
|
data: withLocale(raw),
|
|
@@ -143,7 +167,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
143
167
|
}
|
|
144
168
|
case "dictionary": {
|
|
145
169
|
const raw = await readJson(ref.filePath);
|
|
146
|
-
if (!raw) return
|
|
170
|
+
if (!raw) return null;
|
|
147
171
|
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
148
172
|
id: `${prefix}${key}`,
|
|
149
173
|
data: withLocale({
|
|
@@ -155,7 +179,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
155
179
|
}
|
|
156
180
|
case "document": {
|
|
157
181
|
const text = await readText(ref.filePath);
|
|
158
|
-
if (text === null) return
|
|
182
|
+
if (text === null) return null;
|
|
159
183
|
const { frontmatter, body } = parseFrontmatter(text, stringLikeFieldKeys(model));
|
|
160
184
|
const slug = ref.slug ?? model.id;
|
|
161
185
|
return [{
|
package/dist/astro/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/astro/loader.ts"],"sourcesContent":["// Astro content-layer loader for a `.contentrain` project.\n//\n// `contentrain generate` produces a typed client for application code; Astro\n// wants its content in the content layer instead, so pages can query it with\n// `getCollection()` and render documents with `<Content />`. This loader reads\n// the same `.contentrain` files the generator reads — through the same manifest\n// — so a project never has two answers about what its content is.\n//\n// Astro is NOT a dependency here, not even a peer: the returned object is\n// structurally what `defineCollection({ loader })` accepts, and the context is\n// typed by the small surface this loader actually uses. That keeps\n// @contentrain/query dependency-free and usable from any Astro 5 version.\n\nimport { publicationContext, publicationMeta, isPublishedAt, type PublicationOptions, type PublicationContext } from '../generator/publication.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport { rewriteEntryMedia } from '@contentrain/types'\nimport { isAbsolute, join, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { ContentFileRef } from '../generator/config-reader.js'\nimport { readProjectManifest } from '../generator/config-reader.js'\nimport { readJson, readText } from '../generator/utils.js'\nimport { parseFrontmatter, stringLikeFieldKeys } from '../shared/frontmatter.js'\nimport { resolveMediaRefsInBody } from '../shared/media.js'\n\n/**\n * The slice of Astro's `LoaderContext` this loader uses.\n *\n * Parameter types match Astro's own (5 through 7), and every optional member\n * admits `undefined`: a context type that is narrower than what Astro passes\n * fails `astro check` in a project with `strict` — `create-astro`'s default —\n * or `exactOptionalPropertyTypes`.\n */\nexport interface ContentrainLoaderContext {\n store: {\n clear: () => void\n set: (entry: {\n id: string\n data: Record<string, unknown>\n body?: string\n digest?: string\n rendered?: { html: string, metadata?: Record<string, unknown> }\n filePath?: string\n }) => void\n }\n logger?: { info: (msg: string) => void, warn: (msg: string) => void } | undefined\n /** Astro's resolved config; only `root` is read, to make file paths relative. */\n config?: { root?: URL | string | undefined } | undefined\n generateDigest?: ((data: Record<string, unknown> | string) => string) | undefined\n parseData?: ((entry: { id: string, data: Record<string, unknown>, filePath?: string }) => Promise<Record<string, unknown>>) | undefined\n renderMarkdown?: ((content: string) => Promise<{ html: string, metadata?: Record<string, unknown> }>) | undefined\n watcher?: { add: (path: string) => void, on: (event: string, cb: (path: string) => void) => void } | undefined\n}\n\n/** Structurally Astro's `Loader`. */\nexport interface ContentrainLoader {\n name: string\n load: (context: ContentrainLoaderContext) => Promise<void>\n}\n\nexport interface ContentrainLoaderOptions extends PublicationOptions {\n /** Model id — the `id` in `.contentrain/models/<id>.json`. */\n model: string\n /** Project root holding `.contentrain`. Default: `process.cwd()`. */\n root?: string\n /**\n * Load only this locale. Omitted on an i18n model, every supported locale is\n * loaded and entry ids are prefixed (`en/my-post`) so the two languages of one\n * post do not collide — Astro ids are unique per collection.\n */\n locale?: string\n /**\n * Public media delivery base for resolving relative `media/...` references\n * to absolute URLs. This loader reads `.contentrain` directly — nothing\n * upstream has rewritten them yet — so, unlike CDN-bundled content, they\n * need a resolver here. Applied automatically to image/video/file fields,\n * to `](media/...)` references inside markdown/richtext fields, and to a\n * document's markdown body. Omit to keep values exactly as stored.\n */\n mediaBaseUrl?: string\n}\n\ninterface Entry {\n id: string\n data: Record<string, unknown>\n body?: string\n filePath: string\n}\n\n/**\n * A loader for one Contentrain model.\n *\n * ```ts\n * // src/content.config.ts\n * import { defineCollection } from 'astro:content'\n * import { contentrainLoader } from '@contentrain/query/astro'\n *\n * export const collections = {\n * posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),\n * articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),\n * }\n * ```\n *\n * Each model kind maps to the shape Astro can actually use:\n * collection → one entry per record · document → one entry per file, markdown\n * in `body` (and rendered, so `<Content />` works) · dictionary → one entry per\n * key, because a single blob would make `getEntry()` useless · singleton → one\n * entry, id `<model>` or the locale.\n */\nexport function contentrainLoader(options: ContentrainLoaderOptions): ContentrainLoader {\n const root = options.root ?? process.cwd()\n let watching = false\n\n return {\n name: `contentrain:${options.model}`,\n async load(context) {\n const manifest = await readProjectManifest(root)\n const model = manifest.models.find(m => m.id === options.model)\n if (!model) {\n const known = manifest.models.map(m => m.id).join(', ') || 'none'\n throw new Error(\n `contentrainLoader: model \"${options.model}\" not found in ${join(root, '.contentrain', 'models')}. Models here: ${known}.`,\n )\n }\n\n const refs = manifest.contentFiles.filter(ref =>\n ref.modelId === model.id && (options.locale === undefined || ref.locale === options.locale),\n )\n if (options.locale !== undefined && refs.length === 0 && model.i18n) {\n context.logger?.warn(\n `contentrainLoader: model \"${model.id}\" has no content for locale \"${options.locale}\" — collection is empty.`,\n )\n }\n // Prefix ids only when one collection really holds several languages;\n // a single-locale load keeps the natural id so routes stay clean.\n const prefixLocale = model.i18n === true && options.locale === undefined\n\n const publication = publicationContext(root, manifest.config.locales.default, options)\n const perFile = await Promise.all(refs.map(ref => entriesOf(ref, model, prefixLocale, publication)))\n const entries = options.mediaBaseUrl\n ? resolveEntriesMedia(perFile.flat(), model, options.mediaBaseUrl)\n : perFile.flat()\n // Astro rejects an absolute filePath (\"must be relative to the site\n // root\"), and a `.contentrain` outside the Astro root has no relative\n // form it accepts — there we send none rather than a path it refuses.\n const siteRoot = toPath(context.config?.root)\n\n context.store.clear()\n for (const entry of entries) {\n const data = context.parseData\n ? await context.parseData({ id: entry.id, data: entry.data, filePath: entry.filePath })\n : entry.data\n const filePath = siteRoot === undefined ? undefined : relative(siteRoot, entry.filePath)\n context.store.set({\n id: entry.id,\n data,\n ...(filePath === undefined || filePath.startsWith('..') || isAbsolute(filePath) ? {} : { filePath }),\n ...(entry.body === undefined ? {} : { body: entry.body }),\n ...(context.generateDigest ? { digest: context.generateDigest(entry.data) } : {}),\n ...(entry.body !== undefined && context.renderMarkdown\n ? { rendered: await context.renderMarkdown(entry.body) }\n : {}),\n })\n }\n context.logger?.info(`Loaded ${entries.length} ${model.kind} ${entries.length === 1 ? 'entry' : 'entries'} from ${model.id}`)\n\n // Dev server: reload when the files behind this collection change. The\n // listener is registered once — `load` runs again on every change, and a\n // listener per run would multiply the work on every save.\n if (context.watcher && !watching) {\n watching = true\n const paths = new Set(refs.map(ref => ref.filePath))\n for (const path of paths) context.watcher.add(path)\n const metaDir = join(root, '.contentrain', 'meta', model.id)\n context.watcher.add(metaDir)\n context.watcher.add(join(root, '.contentrain', 'models', `${model.id}.json`))\n const reload = (path: string) => {\n if (!paths.has(path) && !path.startsWith(metaDir + '/') && !path.endsWith(`${model.id}.json`)) return\n void this.load(context)\n }\n context.watcher.on('change', reload)\n context.watcher.on('add', reload)\n context.watcher.on('unlink', reload)\n }\n },\n }\n}\n\n/** Astro hands `config.root` as a file URL; tests and other hosts may use a path. */\nfunction toPath(root: URL | string | undefined): string | undefined {\n if (root === undefined) return undefined\n if (typeof root !== 'string') return fileURLToPath(root)\n return root.startsWith('file:') ? fileURLToPath(root) : root\n}\n\nconst MEDIA_BODY_TYPES = new Set(['markdown', 'richtext'])\n\nfunction fieldNamesOfType(model: ModelDefinition, types: Set<string>): string[] {\n if (!model.fields) return []\n return Object.entries(model.fields).filter(([, f]) => types.has(f.type)).map(([name]) => name)\n}\n\n/** Resolves `media/...` references to absolute URLs — image/video/file field\n * values (including those nested inside object/array fields, via the shared\n * `@contentrain/types` engine), `](media/...)` links and inline HTML\n * src/href inside markdown/richtext fields, and a document's markdown body —\n * across every loaded entry. A model with no fields and no body is returned\n * unchanged, entry by entry. */\nfunction resolveEntriesMedia(entries: Entry[], model: ModelDefinition, mediaBaseUrl: string): Entry[] {\n const bodyFields = fieldNamesOfType(model, MEDIA_BODY_TYPES)\n if (!model.fields && model.kind !== 'document') return entries\n\n return entries.map((entry) => {\n const data = model.fields ? rewriteEntryMedia(entry.data, model.fields, mediaBaseUrl) : entry.data\n for (const field of bodyFields) {\n if (field in data) data[field] = resolveMediaRefsInBody(data[field], mediaBaseUrl)\n }\n const body = entry.body === undefined ? undefined : resolveMediaRefsInBody(entry.body, mediaBaseUrl) as string\n return { ...entry, data, ...(body === undefined ? {} : { body }) }\n })\n}\n\nasync function entriesOf(ref: ContentFileRef, model: ModelDefinition, prefixLocale: boolean, publication?: PublicationContext): Promise<Entry[]> {\n const meta = publication ? await publicationMeta(ref, model, publication) : undefined\n const visible = (id?: string) => !publication || isPublishedAt(id === undefined ? meta : meta?.[id], publication.at, publication.requireStatus)\n // Dictionary meta is one record for the whole file, as for a singleton or document.\n if (model.kind !== 'collection' && !visible()) return []\n const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : ''\n const withLocale = (data: Record<string, unknown>): Record<string, unknown> =>\n ref.locale === null ? data : { ...data, locale: ref.locale }\n\n switch (ref.kind) {\n case 'collection': {\n const raw = await readJson<Record<string, Record<string, unknown>>>(ref.filePath)\n if (!raw) return []\n return Object.entries(raw)\n .filter(([id]) => visible(id))\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([id, fields]) => ({\n id: `${prefix}${id}`,\n data: withLocale({ id, ...fields }),\n filePath: ref.filePath,\n }))\n }\n\n case 'singleton': {\n const raw = await readJson<Record<string, unknown>>(ref.filePath)\n if (!raw) return []\n return [{\n id: prefix ? `${ref.locale}` : model.id,\n data: withLocale(raw),\n filePath: ref.filePath,\n }]\n }\n\n case 'dictionary': {\n // One entry per key: a dictionary key is a semantic address, and a single\n // blob entry would make getEntry() and reference() useless on it.\n const raw = await readJson<Record<string, string>>(ref.filePath)\n if (!raw) return []\n return Object.entries(raw)\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([key, value]) => ({\n id: `${prefix}${key}`,\n data: withLocale({ key, value }),\n filePath: ref.filePath,\n }))\n }\n\n case 'document': {\n const text = await readText(ref.filePath)\n if (text === null) return []\n const { frontmatter, body } = parseFrontmatter(text, stringLikeFieldKeys(model))\n const slug = ref.slug ?? model.id\n return [{\n id: `${prefix}${slug}`,\n data: withLocale({ slug, ...frontmatter }),\n body,\n filePath: ref.filePath,\n }]\n }\n\n default:\n return []\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/astro/loader.ts"],"sourcesContent":["// Astro content-layer loader for a `.contentrain` project.\n//\n// `contentrain generate` produces a typed client for application code; Astro\n// wants its content in the content layer instead, so pages can query it with\n// `getCollection()` and render documents with `<Content />`. This loader reads\n// the same `.contentrain` files the generator reads — through the same manifest\n// — so a project never has two answers about what its content is.\n//\n// Astro is NOT a dependency here, not even a peer: the returned object is\n// structurally what `defineCollection({ loader })` accepts, and the context is\n// typed by the small surface this loader actually uses. That keeps\n// @contentrain/query dependency-free and usable from any Astro 5 version.\n\nimport { publicationContext, publicationMeta, isPublishedAt, type PublicationOptions, type PublicationContext } from '../generator/publication.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport { rewriteEntryMedia } from '@contentrain/types'\nimport { isAbsolute, join, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { ContentFileRef } from '../generator/config-reader.js'\nimport { readProjectManifest, type ProjectManifest } from '../generator/config-reader.js'\nimport { readJson, readText } from '../generator/utils.js'\nimport { parseFrontmatter, stringLikeFieldKeys } from '../shared/frontmatter.js'\nimport { resolveMediaRefsInBody } from '../shared/media.js'\n\n/**\n * The slice of Astro's `LoaderContext` this loader uses.\n *\n * Parameter types match Astro's own (5 through 7), and every optional member\n * admits `undefined`: a context type that is narrower than what Astro passes\n * fails `astro check` in a project with `strict` — `create-astro`'s default —\n * or `exactOptionalPropertyTypes`.\n */\nexport interface ContentrainLoaderContext {\n store: {\n clear: () => void\n set: (entry: {\n id: string\n data: Record<string, unknown>\n body?: string\n digest?: string\n rendered?: { html: string, metadata?: Record<string, unknown> }\n filePath?: string\n }) => void\n }\n logger?: { info: (msg: string) => void, warn: (msg: string) => void } | undefined\n /** Astro's resolved config; only `root` is read, to make file paths relative. */\n config?: { root?: URL | string | undefined } | undefined\n generateDigest?: ((data: Record<string, unknown> | string) => string) | undefined\n parseData?: ((entry: { id: string, data: Record<string, unknown>, filePath?: string }) => Promise<Record<string, unknown>>) | undefined\n renderMarkdown?: ((content: string) => Promise<{ html: string, metadata?: Record<string, unknown> }>) | undefined\n watcher?: { add: (path: string) => void, on: (event: string, cb: (path: string) => void) => void } | undefined\n}\n\n/** Structurally Astro's `Loader`. */\nexport interface ContentrainLoader {\n name: string\n load: (context: ContentrainLoaderContext) => Promise<void>\n}\n\nexport interface ContentrainLoaderOptions extends PublicationOptions {\n /** Model id — the `id` in `.contentrain/models/<id>.json`. */\n model: string\n /** Project root holding `.contentrain`. Default: `process.cwd()`. */\n root?: string\n /**\n * Load only this locale. Omitted on an i18n model, every supported locale is\n * loaded and entry ids are prefixed (`en/my-post`) so the two languages of one\n * post do not collide — Astro ids are unique per collection.\n */\n locale?: string\n /**\n * Public media delivery base for resolving relative `media/...` references\n * to absolute URLs. This loader reads `.contentrain` directly — nothing\n * upstream has rewritten them yet — so, unlike CDN-bundled content, they\n * need a resolver here. Applied automatically to image/video/file fields,\n * to `](media/...)` references inside markdown/richtext fields, and to a\n * document's markdown body. Omit to keep values exactly as stored.\n */\n mediaBaseUrl?: string\n}\n\ninterface Entry {\n id: string\n data: Record<string, unknown>\n body?: string\n filePath: string\n}\n\n/**\n * A loader for one Contentrain model.\n *\n * ```ts\n * // src/content.config.ts\n * import { defineCollection } from 'astro:content'\n * import { contentrainLoader } from '@contentrain/query/astro'\n *\n * export const collections = {\n * posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),\n * articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),\n * }\n * ```\n *\n * Each model kind maps to the shape Astro can actually use:\n * collection → one entry per record · document → one entry per file, markdown\n * in `body` (and rendered, so `<Content />` works) · dictionary → one entry per\n * key, because a single blob would make `getEntry()` useless · singleton → one\n * entry, id `<model>` or the locale.\n */\n/**\n * One manifest per project root for each Astro sync. Astro starts every collection's loader at once and hands them\n * the same config object, so the loaders of one build share a single read of the models and content files instead\n * of each reading them all again. The dev server re-reads on every load (it reloads when a file changes); without a\n * config, as in a test, nothing is shared.\n */\nconst manifests = new WeakMap<object, Map<string, Promise<ProjectManifest>>>()\n\nfunction manifestFor(root: string, context: ContentrainLoaderContext): Promise<ProjectManifest> {\n const config = context.config\n if (context.watcher || !config) return readProjectManifest(root)\n let byRoot = manifests.get(config)\n if (!byRoot) manifests.set(config, byRoot = new Map())\n let manifest = byRoot.get(root)\n if (!manifest) {\n manifest = readProjectManifest(root)\n byRoot.set(root, manifest)\n // A failed read is not kept: the next load tries again.\n manifest.catch(() => byRoot!.delete(root))\n }\n return manifest\n}\n\nexport function contentrainLoader(options: ContentrainLoaderOptions): ContentrainLoader {\n const root = options.root ?? process.cwd()\n let watching = false\n\n return {\n name: `contentrain:${options.model}`,\n async load(context) {\n const manifest = await manifestFor(root, context)\n const model = manifest.models.find(m => m.id === options.model)\n if (!model) {\n const known = manifest.models.map(m => m.id).join(', ') || 'none'\n throw new Error(\n `contentrainLoader: model \"${options.model}\" not found in ${join(root, '.contentrain', 'models')}. Models here: ${known}.`,\n )\n }\n\n const refs = manifest.contentFiles.filter(ref =>\n ref.modelId === model.id && (options.locale === undefined || ref.locale === options.locale),\n )\n if (options.locale !== undefined && refs.length === 0 && model.i18n) {\n context.logger?.warn(\n `contentrainLoader: model \"${model.id}\" has no content for locale \"${options.locale}\" — collection is empty.`,\n )\n }\n // Prefix ids only when one collection really holds several languages;\n // a single-locale load keeps the natural id so routes stay clean.\n const prefixLocale = model.i18n === true && options.locale === undefined\n\n const publication = publicationContext(root, manifest.config.locales.default, options)\n const perFile = await Promise.all(refs.map(ref => entriesOf(ref, model, prefixLocale, publication)))\n // A content file that is there but reads as nothing (not valid JSON) loads no entries: say so, or the\n // collection silently comes out short.\n const unread = refs.filter((_, i) => perFile[i] === null)\n if (unread.length) {\n context.logger?.warn(\n `contentrainLoader: model \"${model.id}\": ${refs.length} content file${refs.length === 1 ? '' : 's'} found, ${refs.length - unread.length} read — `\n + `no entries from ${unread.map(ref => relative(root, ref.filePath)).join(', ')} (not valid JSON).`,\n )\n }\n const loaded = perFile.flatMap(file => file ?? [])\n const entries = options.mediaBaseUrl\n ? resolveEntriesMedia(loaded, model, options.mediaBaseUrl)\n : loaded\n // Astro rejects an absolute filePath (\"must be relative to the site\n // root\"), and a `.contentrain` outside the Astro root has no relative\n // form it accepts — there we send none rather than a path it refuses.\n const siteRoot = toPath(context.config?.root)\n\n context.store.clear()\n for (const entry of entries) {\n const data = context.parseData\n ? await context.parseData({ id: entry.id, data: entry.data, filePath: entry.filePath })\n : entry.data\n const filePath = siteRoot === undefined ? undefined : relative(siteRoot, entry.filePath)\n context.store.set({\n id: entry.id,\n data,\n ...(filePath === undefined || filePath.startsWith('..') || isAbsolute(filePath) ? {} : { filePath }),\n ...(entry.body === undefined ? {} : { body: entry.body }),\n ...(context.generateDigest ? { digest: context.generateDigest(entry.data) } : {}),\n ...(entry.body !== undefined && context.renderMarkdown\n ? { rendered: await context.renderMarkdown(entry.body) }\n : {}),\n })\n }\n context.logger?.info(`Loaded ${entries.length} ${model.kind} ${entries.length === 1 ? 'entry' : 'entries'} from ${model.id}`)\n\n // Dev server: reload when the files behind this collection change. The\n // listener is registered once — `load` runs again on every change, and a\n // listener per run would multiply the work on every save.\n if (context.watcher && !watching) {\n watching = true\n const paths = new Set(refs.map(ref => ref.filePath))\n for (const path of paths) context.watcher.add(path)\n const metaDir = join(root, '.contentrain', 'meta', model.id)\n context.watcher.add(metaDir)\n context.watcher.add(join(root, '.contentrain', 'models', `${model.id}.json`))\n const reload = (path: string) => {\n if (!paths.has(path) && !path.startsWith(metaDir + '/') && !path.endsWith(`${model.id}.json`)) return\n void this.load(context)\n }\n context.watcher.on('change', reload)\n context.watcher.on('add', reload)\n context.watcher.on('unlink', reload)\n }\n },\n }\n}\n\n/** Astro hands `config.root` as a file URL; tests and other hosts may use a path. */\nfunction toPath(root: URL | string | undefined): string | undefined {\n if (root === undefined) return undefined\n if (typeof root !== 'string') return fileURLToPath(root)\n return root.startsWith('file:') ? fileURLToPath(root) : root\n}\n\nconst MEDIA_BODY_TYPES = new Set(['markdown', 'richtext'])\n\nfunction fieldNamesOfType(model: ModelDefinition, types: Set<string>): string[] {\n if (!model.fields) return []\n return Object.entries(model.fields).filter(([, f]) => types.has(f.type)).map(([name]) => name)\n}\n\n/** Resolves `media/...` references to absolute URLs — image/video/file field\n * values (including those nested inside object/array fields, via the shared\n * `@contentrain/types` engine), `](media/...)` links and inline HTML\n * src/href inside markdown/richtext fields, and a document's markdown body —\n * across every loaded entry. A model with no fields and no body is returned\n * unchanged, entry by entry. */\nfunction resolveEntriesMedia(entries: Entry[], model: ModelDefinition, mediaBaseUrl: string): Entry[] {\n const bodyFields = fieldNamesOfType(model, MEDIA_BODY_TYPES)\n if (!model.fields && model.kind !== 'document') return entries\n\n return entries.map((entry) => {\n const data = model.fields ? rewriteEntryMedia(entry.data, model.fields, mediaBaseUrl) : entry.data\n for (const field of bodyFields) {\n if (field in data) data[field] = resolveMediaRefsInBody(data[field], mediaBaseUrl)\n }\n const body = entry.body === undefined ? undefined : resolveMediaRefsInBody(entry.body, mediaBaseUrl) as string\n return { ...entry, data, ...(body === undefined ? {} : { body }) }\n })\n}\n\n/** The file's entries a build may show; null when the file reads as nothing (not valid JSON, or gone since it was listed). */\nasync function entriesOf(ref: ContentFileRef, model: ModelDefinition, prefixLocale: boolean, publication?: PublicationContext): Promise<Entry[] | null> {\n const meta = publication ? await publicationMeta(ref, model, publication) : undefined\n const visible = (id?: string) => !publication || isPublishedAt(id === undefined ? meta : meta?.[id], publication.at, publication.requireStatus)\n // Dictionary meta is one record for the whole file, as for a singleton or document.\n if (model.kind !== 'collection' && !visible()) return []\n const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : ''\n const withLocale = (data: Record<string, unknown>): Record<string, unknown> =>\n ref.locale === null ? data : { ...data, locale: ref.locale }\n\n switch (ref.kind) {\n case 'collection': {\n const raw = await readJson<Record<string, Record<string, unknown>>>(ref.filePath)\n if (!raw) return null\n return Object.entries(raw)\n .filter(([id]) => visible(id))\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([id, fields]) => ({\n id: `${prefix}${id}`,\n data: withLocale({ id, ...fields }),\n filePath: ref.filePath,\n }))\n }\n\n case 'singleton': {\n const raw = await readJson<Record<string, unknown>>(ref.filePath)\n if (!raw) return null\n return [{\n id: prefix ? `${ref.locale}` : model.id,\n data: withLocale(raw),\n filePath: ref.filePath,\n }]\n }\n\n case 'dictionary': {\n // One entry per key: a dictionary key is a semantic address, and a single\n // blob entry would make getEntry() and reference() useless on it.\n const raw = await readJson<Record<string, string>>(ref.filePath)\n if (!raw) return null\n return Object.entries(raw)\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([key, value]) => ({\n id: `${prefix}${key}`,\n data: withLocale({ key, value }),\n filePath: ref.filePath,\n }))\n }\n\n case 'document': {\n const text = await readText(ref.filePath)\n if (text === null) return null\n const { frontmatter, body } = parseFrontmatter(text, stringLikeFieldKeys(model))\n const slug = ref.slug ?? model.id\n return [{\n id: `${prefix}${slug}`,\n data: withLocale({ slug, ...frontmatter }),\n body,\n filePath: ref.filePath,\n }]\n }\n\n default:\n return []\n }\n}\n"],"mappings":"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"}
|
package/dist/cli.cjs
CHANGED
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
import { parseFrontmatterScalar, parseFrontmatterScalarString, splitFrontmatterList } from "@contentrain/types";
|
|
2
|
-
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
let open = 0;
|
|
5
|
+
const waiting = [];
|
|
6
|
+
/** Runs `read` once fewer than {@link MAX_OPEN_READS} reads are in flight. Only for leaf reads: a read must not wait on another. */
|
|
7
|
+
async function limitRead(read) {
|
|
8
|
+
if (open >= 64) await new Promise((resolve) => waiting.push(resolve));
|
|
9
|
+
open++;
|
|
10
|
+
try {
|
|
11
|
+
return await read();
|
|
12
|
+
} finally {
|
|
13
|
+
open--;
|
|
14
|
+
waiting.shift()?.();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** A path that is not there (or whose parent is a file) reads as absent; any other failure is a real error. */
|
|
18
|
+
function isMissing(error) {
|
|
19
|
+
const code = error?.code;
|
|
20
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The file's JSON, or null when it is not there or is not valid JSON. Any other read failure (EMFILE, EACCES, EIO)
|
|
24
|
+
* throws: reading it as null would drop a model or its content from the build without a word.
|
|
25
|
+
*/
|
|
26
|
+
async function readJson(filePath) {
|
|
27
|
+
const raw = await readText(filePath);
|
|
28
|
+
if (raw === null) return null;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** The directory's entries, or none when it is not there. Any other failure throws. */
|
|
36
|
+
async function readDir(dirPath) {
|
|
37
|
+
try {
|
|
38
|
+
return await limitRead(() => readdir(dirPath));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (isMissing(error)) return [];
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Whether the file is there. Any failure other than its absence throws. */
|
|
45
|
+
async function fileExists(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
return (await limitRead(() => stat(filePath))).isFile();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (isMissing(error)) return false;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** The file's text, or null when it is not there. Any other failure throws. */
|
|
54
|
+
async function readText(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
return await limitRead(() => readFile(filePath, "utf-8"));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (isMissing(error)) return null;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function writeText(filePath, content) {
|
|
63
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
64
|
+
await writeFile(filePath, content, "utf-8");
|
|
65
|
+
}
|
|
66
|
+
function contentrainDir(projectRoot) {
|
|
67
|
+
return join(projectRoot, ".contentrain");
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
4
70
|
//#region src/generator/publication.ts
|
|
5
71
|
function publicationContext(projectRoot, defaultLocale, options) {
|
|
6
72
|
if (!options.publishedOnly && !options.requireStatus && options.at === void 0) return void 0;
|
|
@@ -27,7 +93,7 @@ async function publicationMeta(ref, model, context) {
|
|
|
27
93
|
const path = join(...parts, `${locale}.json`);
|
|
28
94
|
let text;
|
|
29
95
|
try {
|
|
30
|
-
text = await readFile(path, "utf8");
|
|
96
|
+
text = await limitRead(() => readFile(path, "utf8"));
|
|
31
97
|
} catch (error) {
|
|
32
98
|
if (error.code === "ENOENT") return void 0;
|
|
33
99
|
throw error;
|
|
@@ -55,37 +121,6 @@ function isPublishedAt(meta, at, requireStatus = false) {
|
|
|
55
121
|
return true;
|
|
56
122
|
}
|
|
57
123
|
//#endregion
|
|
58
|
-
//#region src/generator/utils.ts
|
|
59
|
-
async function readJson(filePath) {
|
|
60
|
-
try {
|
|
61
|
-
const raw = await readFile(filePath, "utf-8");
|
|
62
|
-
return JSON.parse(raw);
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async function readDir(dirPath) {
|
|
68
|
-
try {
|
|
69
|
-
return await readdir(dirPath);
|
|
70
|
-
} catch {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async function readText(filePath) {
|
|
75
|
-
try {
|
|
76
|
-
return await readFile(filePath, "utf-8");
|
|
77
|
-
} catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function writeText(filePath, content) {
|
|
82
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
83
|
-
await writeFile(filePath, content, "utf-8");
|
|
84
|
-
}
|
|
85
|
-
function contentrainDir(projectRoot) {
|
|
86
|
-
return join(projectRoot, ".contentrain");
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
124
|
//#region src/generator/config-reader.ts
|
|
90
125
|
async function readProjectManifest(projectRoot) {
|
|
91
126
|
const crDir = contentrainDir(projectRoot);
|
|
@@ -133,7 +168,7 @@ async function mapContentFiles(projectRoot, model, config) {
|
|
|
133
168
|
if (model.kind === "document") return mapDocumentFiles(dir, model, config);
|
|
134
169
|
if (model.i18n) return (await Promise.all(config.locales.supported.map(async (locale) => {
|
|
135
170
|
const filePath = jsonFilePath(dir, model, locale);
|
|
136
|
-
if (await
|
|
171
|
+
if (!await fileExists(filePath)) return null;
|
|
137
172
|
return {
|
|
138
173
|
modelId: model.id,
|
|
139
174
|
locale,
|
|
@@ -142,7 +177,7 @@ async function mapContentFiles(projectRoot, model, config) {
|
|
|
142
177
|
};
|
|
143
178
|
}))).filter(Boolean);
|
|
144
179
|
const filePath = join(dir, "data.json");
|
|
145
|
-
if (await
|
|
180
|
+
if (!await fileExists(filePath)) return [];
|
|
146
181
|
return [{
|
|
147
182
|
modelId: model.id,
|
|
148
183
|
locale: null,
|
|
@@ -166,7 +201,7 @@ async function mapDocumentLocale(dir, model, locale, strategy) {
|
|
|
166
201
|
const entries = await readDir(dir);
|
|
167
202
|
return (await Promise.all(entries.map(async (entry) => {
|
|
168
203
|
const filePath = join(dir, entry, `${locale}.md`);
|
|
169
|
-
if (await
|
|
204
|
+
if (!await fileExists(filePath)) return null;
|
|
170
205
|
return {
|
|
171
206
|
modelId: model.id,
|
|
172
207
|
locale,
|
|
@@ -295,6 +330,6 @@ function parseValue(raw, forceString = false) {
|
|
|
295
330
|
return forceString ? parseFrontmatterScalarString(raw) : parseFrontmatterScalar(raw);
|
|
296
331
|
}
|
|
297
332
|
//#endregion
|
|
298
|
-
export {
|
|
333
|
+
export { publicationContext as a, readJson as c, isPublishedAt as i, readText as l, stringLikeFieldKeys as n, publicationMeta as o, readProjectManifest as r, readDir as s, parseFrontmatter as t, writeText as u };
|
|
299
334
|
|
|
300
|
-
//# sourceMappingURL=frontmatter-
|
|
335
|
+
//# sourceMappingURL=frontmatter-BGQD38mL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-BGQD38mL.mjs","names":[],"sources":["../src/generator/utils.ts","../src/generator/publication.ts","../src/generator/config-reader.ts","../src/shared/frontmatter.ts"],"sourcesContent":["import { readFile, readdir, stat, writeFile, mkdir } from 'node:fs/promises'\nimport { join, dirname } from 'node:path'\n\n/**\n * At most this many files open at once for reading, across every caller in the process. Astro starts every\n * collection's loader together, and each reads the project's models and content: a site with hundreds of models\n * asked for hundreds of thousands of files at the same moment and ran out of file descriptors (EMFILE).\n */\nexport const MAX_OPEN_READS = 64\n\nlet open = 0\nconst waiting: Array<() => void> = []\n\n/** Runs `read` once fewer than {@link MAX_OPEN_READS} reads are in flight. Only for leaf reads: a read must not wait on another. */\nexport async function limitRead<T>(read: () => Promise<T>): Promise<T> {\n if (open >= MAX_OPEN_READS) await new Promise<void>(resolve => waiting.push(resolve))\n open++\n try {\n return await read()\n }\n finally {\n open--\n waiting.shift()?.()\n }\n}\n\n/** A path that is not there (or whose parent is a file) reads as absent; any other failure is a real error. */\nfunction isMissing(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException | undefined)?.code\n return code === 'ENOENT' || code === 'ENOTDIR'\n}\n\n/**\n * The file's JSON, or null when it is not there or is not valid JSON. Any other read failure (EMFILE, EACCES, EIO)\n * throws: reading it as null would drop a model or its content from the build without a word.\n */\nexport async function readJson<T>(filePath: string): Promise<T | null> {\n const raw = await readText(filePath)\n if (raw === null) return null\n try {\n return JSON.parse(raw) as T\n }\n catch {\n return null\n }\n}\n\n/** The directory's entries, or none when it is not there. Any other failure throws. */\nexport async function readDir(dirPath: string): Promise<string[]> {\n try {\n return await limitRead(() => readdir(dirPath))\n }\n catch (error) {\n if (isMissing(error)) return []\n throw error\n }\n}\n\n/** Whether the file is there. Any failure other than its absence throws. */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n return (await limitRead(() => stat(filePath))).isFile()\n }\n catch (error) {\n if (isMissing(error)) return false\n throw error\n }\n}\n\n/** The file's text, or null when it is not there. Any other failure throws. */\nexport async function readText(filePath: string): Promise<string | null> {\n try {\n return await limitRead(() => readFile(filePath, 'utf-8'))\n }\n catch (error) {\n if (isMissing(error)) return null\n throw error\n }\n}\n\nexport async function writeText(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true })\n await writeFile(filePath, content, 'utf-8')\n}\n\nexport function contentrainDir(projectRoot: string): string {\n return join(projectRoot, '.contentrain')\n}\n","import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { ModelDefinition } from '@contentrain/types'\nimport type { ContentFileRef } from './config-reader.js'\nimport { limitRead } from './utils.js'\n\nexport interface PublicationOptions {\n /** Opt in for public builds; the default keeps the existing editorial client. */\n publishedOnly?: boolean\n /** ISO timestamp for a reproducible public build. Implies publishedOnly. */\n at?: string\n /**\n * Hold back an entry that carries no workflow status — no meta record, or a\n * record without `status`. Without it such an entry stays visible (legacy\n * content predates meta). Set it when the producer writes a status for every\n * entry, as the WordPress import does: a missing status then means a broken\n * import, and a public build must not guess. Implies publishedOnly.\n */\n requireStatus?: boolean\n}\n\nexport interface PublicationContext {\n projectRoot: string\n defaultLocale: string\n at: number\n requireStatus: boolean\n}\n\nexport function publicationContext(projectRoot: string, defaultLocale: string, options: PublicationOptions): PublicationContext | undefined {\n if (!options.publishedOnly && !options.requireStatus && options.at === undefined) return undefined\n if (options.at !== undefined && !/^\\d{4}-\\d{2}-\\d{2}T.*(?:Z|[+-]\\d{2}:\\d{2})$/i.test(options.at))\n throw new Error('Invalid publication --at timestamp: include an ISO date, time and timezone')\n const at = options.at === undefined ? Date.now() : Date.parse(options.at)\n if (!Number.isFinite(at)) throw new Error('Invalid publication --at timestamp')\n return { projectRoot, defaultLocale, at, requireStatus: options.requireStatus === true }\n}\n\n/** Canonical meta is independent of custom content paths and locale strategies. */\nexport async function publicationMeta(ref: ContentFileRef, model: ModelDefinition, context: PublicationContext): Promise<Record<string, unknown> | undefined> {\n const locale = model.i18n ? ref.locale ?? context.defaultLocale : context.defaultLocale\n const parts = [context.projectRoot, '.contentrain', 'meta', model.id]\n if (model.kind === 'document') parts.push(ref.slug ?? model.id)\n const path = join(...parts, `${locale}.json`)\n let text: string\n try { text = await limitRead(() => readFile(path, 'utf8')) }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined\n throw error\n }\n const meta: unknown = JSON.parse(text)\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) throw new Error(`Invalid publication metadata: ${path}`)\n return meta as Record<string, unknown>\n}\n\n/**\n * Legacy content without meta remains visible, matching Studio's CDN contract —\n * unless `requireStatus`, where an entry without a status (missing or empty) is held back.\n */\nexport function isPublishedAt(meta: unknown, at: number, requireStatus = false): boolean {\n if (meta === undefined) return !requireStatus\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) return false\n const value = meta as Record<string, unknown>\n if (!value.status && requireStatus) return false\n if (value.status && value.status !== 'published') return false\n for (const key of ['publish_at', 'expire_at'] as const) {\n if (value[key] === undefined || value[key] === null) continue\n const time = typeof value[key] === 'string' ? Date.parse(value[key]) : NaN\n if (!Number.isFinite(time)) return false\n if (key === 'publish_at' ? time > at : time <= at) return false\n }\n return true\n}\n","import type { ContentrainConfig, ModelDefinition, LocaleStrategy } from '@contentrain/types'\nimport { join } from 'node:path'\nimport { fileExists, readJson, readDir, contentrainDir } from './utils.js'\n\nexport interface ContentFileRef {\n modelId: string\n locale: string | null\n filePath: string\n kind: ModelDefinition['kind']\n /** Extracted slug for document kind (from filename, not frontmatter) */\n slug?: string\n}\n\nexport interface ProjectManifest {\n config: ContentrainConfig\n models: ModelDefinition[]\n contentFiles: ContentFileRef[]\n}\n\nexport async function readProjectManifest(projectRoot: string): Promise<ProjectManifest> {\n const crDir = contentrainDir(projectRoot)\n\n // 1. Read config\n const rawConfig = await readJson<Partial<ContentrainConfig>>(join(crDir, 'config.json'))\n const config: ContentrainConfig = {\n version: rawConfig?.version ?? 1,\n stack: (rawConfig?.stack ?? 'other') as ContentrainConfig['stack'],\n workflow: (rawConfig?.workflow ?? 'auto-merge') as ContentrainConfig['workflow'],\n locales: {\n default: rawConfig?.locales?.default ?? 'en',\n supported: rawConfig?.locales?.supported ?? [rawConfig?.locales?.default ?? 'en'],\n },\n domains: rawConfig?.domains ?? [],\n repository: rawConfig?.repository,\n assets_path: rawConfig?.assets_path,\n cdn: rawConfig?.cdn,\n branchRetention: rawConfig?.branchRetention,\n }\n\n // 2. Read all models (parallel)\n const modelsDir = join(crDir, 'models')\n const modelFiles = (await readDir(modelsDir)).filter(f => f.endsWith('.json'))\n const modelResults = await Promise.all(\n modelFiles.map(file => readJson<ModelDefinition>(join(modelsDir, file))),\n )\n const models = modelResults\n .filter((m): m is ModelDefinition => m != null && Boolean(m.id))\n .toSorted((a, b) => a.id.localeCompare(b.id, 'en'))\n\n // 3. Map content files (parallel per model)\n const allRefs = await Promise.all(\n models.map(model => mapContentFiles(projectRoot, model, config)),\n )\n const contentFiles = allRefs.flat()\n\n return { config, models, contentFiles }\n}\n\nfunction getContentDir(projectRoot: string, model: ModelDefinition): string {\n if (model.content_path) return join(projectRoot, model.content_path)\n return join(contentrainDir(projectRoot), 'content', model.domain, model.id)\n}\n\nfunction getLocaleStrategy(model: ModelDefinition): LocaleStrategy {\n return model.locale_strategy ?? 'file'\n}\n\nfunction jsonFilePath(dir: string, model: ModelDefinition, locale: string): string {\n const strategy = getLocaleStrategy(model)\n switch (strategy) {\n case 'suffix': return join(dir, `${model.id}.${locale}.json`)\n case 'directory': return join(dir, locale, `${model.id}.json`)\n case 'none': return join(dir, `${model.id}.json`)\n case 'file':\n default: return join(dir, `${locale}.json`)\n }\n}\n\nasync function mapContentFiles(\n projectRoot: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n): Promise<ContentFileRef[]> {\n const dir = getContentDir(projectRoot, model)\n\n if (model.kind === 'document') {\n return mapDocumentFiles(dir, model, config)\n }\n\n // JSON-based models (singleton, collection, dictionary)\n if (model.i18n) {\n const results = await Promise.all(\n config.locales.supported.map(async (locale) => {\n const filePath = jsonFilePath(dir, model, locale)\n if (!(await fileExists(filePath))) return null\n return { modelId: model.id, locale, filePath, kind: model.kind } as ContentFileRef\n }),\n )\n return results.filter(Boolean) as ContentFileRef[]\n }\n\n // Non-i18n: always data.json\n const filePath = join(dir, 'data.json')\n if (!(await fileExists(filePath))) return []\n return [{ modelId: model.id, locale: null, filePath, kind: model.kind }]\n}\n\nasync function mapDocumentFiles(\n dir: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n): Promise<ContentFileRef[]> {\n const strategy = getLocaleStrategy(model)\n\n if (!model.i18n) {\n // Non-i18n documents: {dir}/{slug}.md\n const files = (await readDir(dir)).filter(f => f.endsWith('.md'))\n return files.map(f => ({\n modelId: model.id,\n locale: null,\n filePath: join(dir, f),\n kind: 'document' as const,\n slug: f.replace('.md', ''),\n }))\n }\n\n // i18n documents — resolve per locale in parallel\n const allLocaleRefs = await Promise.all(\n config.locales.supported.map(locale => mapDocumentLocale(dir, model, locale, strategy)),\n )\n return allLocaleRefs.flat()\n}\n\nasync function mapDocumentLocale(\n dir: string,\n model: ModelDefinition,\n locale: string,\n strategy: LocaleStrategy,\n): Promise<ContentFileRef[]> {\n if (strategy === 'file') {\n // Each slug is a directory: {dir}/{slug}/{locale}.md\n const entries = await readDir(dir)\n const results = await Promise.all(\n entries.map(async (entry) => {\n const filePath = join(dir, entry, `${locale}.md`)\n if (!(await fileExists(filePath))) return null\n return { modelId: model.id, locale, filePath, kind: 'document', slug: entry } as ContentFileRef\n }),\n )\n return results.filter(Boolean) as ContentFileRef[]\n }\n\n if (strategy === 'directory') {\n // {dir}/{locale}/{slug}.md\n const localeDir = join(dir, locale)\n const files = (await readDir(localeDir)).filter(f => f.endsWith('.md'))\n return files.map(f => ({\n modelId: model.id,\n locale,\n filePath: join(localeDir, f),\n kind: 'document' as const,\n slug: f.replace('.md', ''),\n }))\n }\n\n if (strategy === 'suffix') {\n // {dir}/{slug}.{locale}.md\n const suffix = `.${locale}.md`\n const files = (await readDir(dir)).filter(f => f.endsWith(suffix))\n return files.map(f => ({\n modelId: model.id,\n locale,\n filePath: join(dir, f),\n kind: 'document' as const,\n slug: f.slice(0, -suffix.length),\n }))\n }\n\n return []\n}\n","// Frontmatter parsing shared by the client generator and the Astro loader.\n//\n// Both read the same `.contentrain` documents, so the parser lives here rather\n// than being replicated: a divergence would mean the generated client and the\n// Astro collection disagree about the same file's fields.\n//\n// The same argument reaches one level further out. The content engine reads\n// these documents too, through `@contentrain/types`, and for a while this file\n// carried its own copy of the scalar rules — so the two disagreed about what a\n// quoted string with escapes in it says. The *structure* below (nesting,\n// indentation, the model-aware string coercion) is this package's; the scalar\n// grammar is imported, because how a value is spelled on disk is a contract\n// rather than an implementation detail.\n\nimport type { ModelDefinition } from '@contentrain/types'\nimport {\n parseFrontmatterScalar,\n parseFrontmatterScalarString,\n splitFrontmatterList,\n} from '@contentrain/types'\n\n// Field types that map to `string` in the generated types — their frontmatter\n// values must NOT be numerically coerced (e.g. a string SKU \"007\").\nconst STRING_LIKE_TYPES = new Set([\n 'string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext', 'date', 'datetime', 'image', 'video', 'file',\n 'select', 'relation',\n])\n\nexport function stringLikeFieldKeys(model: ModelDefinition): Set<string> {\n const keys = new Set<string>()\n if (model.fields) {\n for (const [name, field] of Object.entries(model.fields)) {\n if (STRING_LIKE_TYPES.has(field.type)) keys.add(name)\n }\n }\n return keys\n}\n\n// Minimal frontmatter parser (replicated from MCP pattern)\nexport function parseFrontmatter(text: string, stringKeys: Set<string> = new Set()): { frontmatter: Record<string, unknown>; body: string } {\n const normalized = text.replace(/\\r\\n/g, '\\n')\n const match = normalized.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/)\n if (!match) return { frontmatter: {}, body: normalized }\n\n const fmStr = match[1]!\n const body = match[2]!.trim()\n const frontmatter: Record<string, unknown> = {}\n\n // Stack-based parser that handles nested objects and arrays\n const lines = fmStr.split('\\n')\n const stack: Array<{ obj: Record<string, unknown>; indent: number }> = [{ obj: frontmatter, indent: -1 }]\n\n for (const [lineIndex, line] of lines.entries()) {\n // Skip empty lines\n if (line.trim() === '') continue\n\n // Array item\n const arrayMatch = line.match(/^(\\s*)-\\s+(.*)$/)\n if (arrayMatch) {\n const arrIndent = arrayMatch[1]!.length\n const value = arrayMatch[2]!.trim()\n // Find the parent that owns this array\n while (stack.length > 1 && stack[stack.length - 1]!.indent >= arrIndent) {\n stack.pop()\n }\n const parent = stack[stack.length - 1]!.obj\n const lastKey = Object.keys(parent).pop()\n if (lastKey && Array.isArray(parent[lastKey])) {\n (parent[lastKey] as unknown[]).push(parseValue(value))\n }\n continue\n }\n\n // Key-value pair\n const kvMatch = line.match(/^(\\s*)([\\w][\\w.-]*)\\s*:\\s*(.*)$/)\n if (!kvMatch) continue\n\n const kvIndent = kvMatch[1]!.length\n const key = kvMatch[2]!\n const rawValue = kvMatch[3]!.trim()\n\n // Pop stack to find correct parent based on indentation\n while (stack.length > 1 && stack[stack.length - 1]!.indent >= kvIndent) {\n stack.pop()\n }\n const current = stack[stack.length - 1]!.obj\n\n if (rawValue === '') {\n // A bare `key:` is ambiguous: empty array, empty object, or null. It is\n // resolved by what follows — a list item opens an array, a more-indented\n // key opens an object, and anything else (including end of frontmatter)\n // is an empty array, which is what the canonical reader in\n // `@contentrain/types` returns. Documents written today say `key: []`\n // outright; this path is for the ones written before that.\n //\n // The peek used `lines.indexOf(line)`, which finds the FIRST line with\n // this text rather than this one — two identically spelled lines in one\n // document made the second read the first one's successor.\n let nextLine = ''\n for (let i = lineIndex + 1; i < lines.length; i += 1) {\n if (lines[i]!.trim() !== '') {\n nextLine = lines[i]!\n break\n }\n }\n const nextIndent = nextLine.length - nextLine.trimStart().length\n if (nextLine.trim().startsWith('-')) {\n current[key] = []\n } else if (nextLine !== '' && nextIndent > kvIndent) {\n const nested: Record<string, unknown> = {}\n current[key] = nested\n stack.push({ obj: nested, indent: kvIndent })\n } else {\n current[key] = []\n }\n continue\n }\n\n if (rawValue.startsWith('[') && rawValue.endsWith(']')) {\n const inner = rawValue.slice(1, -1).trim()\n current[key] = inner === ''\n ? []\n : splitFrontmatterList(inner).map(item => parseFrontmatterScalarString(item.trim()))\n continue\n }\n\n // Only top-level keys are matched against the model's declared field types.\n const forceString = current === frontmatter && stringKeys.has(key)\n current[key] = parseValue(rawValue, forceString)\n }\n\n return { frontmatter, body }\n}\n\nfunction parseValue(raw: string, forceString = false): unknown {\n return forceString ? parseFrontmatterScalarString(raw) : parseFrontmatterScalar(raw)\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
let _contentrain_types = require("@contentrain/types");
|
|
2
2
|
let node_fs_promises = require("node:fs/promises");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
|
+
let open = 0;
|
|
5
|
+
const waiting = [];
|
|
6
|
+
/** Runs `read` once fewer than {@link MAX_OPEN_READS} reads are in flight. Only for leaf reads: a read must not wait on another. */
|
|
7
|
+
async function limitRead(read) {
|
|
8
|
+
if (open >= 64) await new Promise((resolve) => waiting.push(resolve));
|
|
9
|
+
open++;
|
|
10
|
+
try {
|
|
11
|
+
return await read();
|
|
12
|
+
} finally {
|
|
13
|
+
open--;
|
|
14
|
+
waiting.shift()?.();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** A path that is not there (or whose parent is a file) reads as absent; any other failure is a real error. */
|
|
18
|
+
function isMissing(error) {
|
|
19
|
+
const code = error?.code;
|
|
20
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The file's JSON, or null when it is not there or is not valid JSON. Any other read failure (EMFILE, EACCES, EIO)
|
|
24
|
+
* throws: reading it as null would drop a model or its content from the build without a word.
|
|
25
|
+
*/
|
|
26
|
+
async function readJson(filePath) {
|
|
27
|
+
const raw = await readText(filePath);
|
|
28
|
+
if (raw === null) return null;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** The directory's entries, or none when it is not there. Any other failure throws. */
|
|
36
|
+
async function readDir(dirPath) {
|
|
37
|
+
try {
|
|
38
|
+
return await limitRead(() => (0, node_fs_promises.readdir)(dirPath));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (isMissing(error)) return [];
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Whether the file is there. Any failure other than its absence throws. */
|
|
45
|
+
async function fileExists(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
return (await limitRead(() => (0, node_fs_promises.stat)(filePath))).isFile();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (isMissing(error)) return false;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** The file's text, or null when it is not there. Any other failure throws. */
|
|
54
|
+
async function readText(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
return await limitRead(() => (0, node_fs_promises.readFile)(filePath, "utf-8"));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (isMissing(error)) return null;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function writeText(filePath, content) {
|
|
63
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(filePath), { recursive: true });
|
|
64
|
+
await (0, node_fs_promises.writeFile)(filePath, content, "utf-8");
|
|
65
|
+
}
|
|
66
|
+
function contentrainDir(projectRoot) {
|
|
67
|
+
return (0, node_path.join)(projectRoot, ".contentrain");
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
4
70
|
//#region src/generator/publication.ts
|
|
5
71
|
function publicationContext(projectRoot, defaultLocale, options) {
|
|
6
72
|
if (!options.publishedOnly && !options.requireStatus && options.at === void 0) return void 0;
|
|
@@ -27,7 +93,7 @@ async function publicationMeta(ref, model, context) {
|
|
|
27
93
|
const path = (0, node_path.join)(...parts, `${locale}.json`);
|
|
28
94
|
let text;
|
|
29
95
|
try {
|
|
30
|
-
text = await (0, node_fs_promises.readFile)(path, "utf8");
|
|
96
|
+
text = await limitRead(() => (0, node_fs_promises.readFile)(path, "utf8"));
|
|
31
97
|
} catch (error) {
|
|
32
98
|
if (error.code === "ENOENT") return void 0;
|
|
33
99
|
throw error;
|
|
@@ -55,37 +121,6 @@ function isPublishedAt(meta, at, requireStatus = false) {
|
|
|
55
121
|
return true;
|
|
56
122
|
}
|
|
57
123
|
//#endregion
|
|
58
|
-
//#region src/generator/utils.ts
|
|
59
|
-
async function readJson(filePath) {
|
|
60
|
-
try {
|
|
61
|
-
const raw = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
62
|
-
return JSON.parse(raw);
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async function readDir(dirPath) {
|
|
68
|
-
try {
|
|
69
|
-
return await (0, node_fs_promises.readdir)(dirPath);
|
|
70
|
-
} catch {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async function readText(filePath) {
|
|
75
|
-
try {
|
|
76
|
-
return await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
77
|
-
} catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function writeText(filePath, content) {
|
|
82
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(filePath), { recursive: true });
|
|
83
|
-
await (0, node_fs_promises.writeFile)(filePath, content, "utf-8");
|
|
84
|
-
}
|
|
85
|
-
function contentrainDir(projectRoot) {
|
|
86
|
-
return (0, node_path.join)(projectRoot, ".contentrain");
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
124
|
//#region src/generator/config-reader.ts
|
|
90
125
|
async function readProjectManifest(projectRoot) {
|
|
91
126
|
const crDir = contentrainDir(projectRoot);
|
|
@@ -133,7 +168,7 @@ async function mapContentFiles(projectRoot, model, config) {
|
|
|
133
168
|
if (model.kind === "document") return mapDocumentFiles(dir, model, config);
|
|
134
169
|
if (model.i18n) return (await Promise.all(config.locales.supported.map(async (locale) => {
|
|
135
170
|
const filePath = jsonFilePath(dir, model, locale);
|
|
136
|
-
if (await
|
|
171
|
+
if (!await fileExists(filePath)) return null;
|
|
137
172
|
return {
|
|
138
173
|
modelId: model.id,
|
|
139
174
|
locale,
|
|
@@ -142,7 +177,7 @@ async function mapContentFiles(projectRoot, model, config) {
|
|
|
142
177
|
};
|
|
143
178
|
}))).filter(Boolean);
|
|
144
179
|
const filePath = (0, node_path.join)(dir, "data.json");
|
|
145
|
-
if (await
|
|
180
|
+
if (!await fileExists(filePath)) return [];
|
|
146
181
|
return [{
|
|
147
182
|
modelId: model.id,
|
|
148
183
|
locale: null,
|
|
@@ -166,7 +201,7 @@ async function mapDocumentLocale(dir, model, locale, strategy) {
|
|
|
166
201
|
const entries = await readDir(dir);
|
|
167
202
|
return (await Promise.all(entries.map(async (entry) => {
|
|
168
203
|
const filePath = (0, node_path.join)(dir, entry, `${locale}.md`);
|
|
169
|
-
if (await
|
|
204
|
+
if (!await fileExists(filePath)) return null;
|
|
170
205
|
return {
|
|
171
206
|
modelId: model.id,
|
|
172
207
|
locale,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as publicationContext, c as readJson, i as isPublishedAt, l as readText, n as stringLikeFieldKeys, o as publicationMeta, r as readProjectManifest, s as readDir, t as parseFrontmatter, u as writeText } from "./frontmatter-BGQD38mL.mjs";
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/generator/type-emitter.ts
|
|
@@ -720,4 +720,4 @@ async function generate(options) {
|
|
|
720
720
|
//#endregion
|
|
721
721
|
export { generate as t };
|
|
722
722
|
|
|
723
|
-
//# sourceMappingURL=generate-
|
|
723
|
+
//# sourceMappingURL=generate-1RkU4u-9.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-BEfRkd7j.mjs","names":[],"sources":["../src/generator/type-emitter.ts","../src/generator/data-emitter.ts","../src/generator/runtime-emitter.ts","../src/generator/package-json.ts","../src/generator/generate.ts"],"sourcesContent":["import type { ModelDefinition, FieldDef } from '@contentrain/types'\n\nexport function emitTypes(models: ModelDefinition[], hasMedia = false): string {\n const lines: string[] = [\n '/* eslint-disable */',\n '/* oxlint-disable */',\n '// Auto-generated by @contentrain/query — do not edit manually',\n '// Re-generate with: npx contentrain-query generate',\n '',\n \"export type ContentStatus = 'draft' | 'published' | 'in_review' | 'rejected' | 'archived'\",\n \"export type WhereOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains'\",\n '',\n ]\n\n // Model interfaces\n for (const model of models) {\n if (model.kind === 'dictionary') {\n lines.push(`export type ${kebabToPascal(model.id)} = Record<string, string>`)\n } else {\n lines.push(`export interface ${kebabToPascal(model.id)} {`)\n // System fields by kind. Track their names so a model-defined field of\n // the same name is not emitted twice (the base field wins).\n const baseNames = new Set<string>()\n if (model.kind === 'collection') {\n lines.push(' id: string')\n baseNames.add('id')\n }\n if (model.kind === 'document') {\n lines.push(' slug: string')\n lines.push(' body: string')\n baseNames.add('slug')\n baseNames.add('body')\n }\n // User fields\n if (model.fields) {\n for (const [name, field] of Object.entries(model.fields)) {\n if (baseNames.has(name)) continue\n const tsType = fieldToTS(field)\n const optional = field.required ? '' : '?'\n lines.push(` ${name}${optional}: ${tsType}`)\n }\n }\n lines.push('}')\n }\n lines.push('')\n }\n\n // Query interfaces\n lines.push(`export interface QueryBuilder<T> {\n locale(lang: string): QueryBuilder<T>\n where<K extends keyof T>(field: K, value: T[K]): QueryBuilder<T>\n where<K extends keyof T>(field: K, op: WhereOp, value: unknown): QueryBuilder<T>\n sort<K extends keyof T>(field: K, order?: 'asc' | 'desc'): QueryBuilder<T>\n limit(n: number): QueryBuilder<T>\n offset(n: number): QueryBuilder<T>\n include<K extends keyof T & string>(...fields: K[]): QueryBuilder<T>\n count(): number\n first(): T | undefined\n all(): T[]\n}`)\n lines.push('')\n\n lines.push(`export interface SingletonAccessor<T> {\n locale(lang: string): SingletonAccessor<T>\n include<K extends keyof T & string>(...fields: K[]): SingletonAccessor<T>\n get(): T\n}`)\n lines.push('')\n\n lines.push(`export interface DictionaryAccessor {\n locale(lang: string): DictionaryAccessor\n get(): Record<string, string>\n get(key: string): string | undefined\n get(key: string, params: Record<string, string | number>): string\n}`)\n lines.push('')\n\n lines.push(`export interface DocumentQuery<T> {\n locale(lang: string): DocumentQuery<T>\n where<K extends keyof T>(field: K, value: T[K]): DocumentQuery<T>\n where<K extends keyof T>(field: K, op: WhereOp, value: unknown): DocumentQuery<T>\n sort<K extends keyof T>(field: K, order?: 'asc' | 'desc'): DocumentQuery<T>\n include<K extends keyof T & string>(...fields: K[]): DocumentQuery<T>\n bySlug(slug: string): T | undefined\n count(): number\n first(): T | undefined\n all(): T[]\n}`)\n lines.push('')\n\n // Overloaded entry points\n const collections = models.filter(m => m.kind === 'collection')\n const singletons = models.filter(m => m.kind === 'singleton')\n const dictionaries = models.filter(m => m.kind === 'dictionary')\n const documents = models.filter(m => m.kind === 'document')\n\n // query() overloads\n for (const m of collections) {\n lines.push(`export declare function query(model: '${m.id}'): QueryBuilder<${kebabToPascal(m.id)}>`)\n }\n lines.push('export declare function query(model: string): QueryBuilder<Record<string, unknown>>')\n lines.push('')\n\n // singleton() overloads\n for (const m of singletons) {\n lines.push(`export declare function singleton(model: '${m.id}'): SingletonAccessor<${kebabToPascal(m.id)}>`)\n }\n lines.push('export declare function singleton(model: string): SingletonAccessor<Record<string, unknown>>')\n lines.push('')\n\n // dictionary() overloads\n for (const m of dictionaries) {\n lines.push(`export declare function dictionary(model: '${m.id}'): DictionaryAccessor`)\n }\n lines.push('export declare function dictionary(model: string): DictionaryAccessor')\n lines.push('')\n\n // document() overloads\n for (const m of documents) {\n lines.push(`export declare function document(model: '${m.id}'): DocumentQuery<${kebabToPascal(m.id)}>`)\n }\n lines.push('export declare function document(model: string): DocumentQuery<Record<string, unknown>>')\n lines.push('')\n\n // media()/mediaBody() resolvers — emitted only when a media delivery base\n // is configured.\n if (hasMedia) {\n lines.push('/** Resolve a stored `media/...` path to its absolute delivery URL. External URLs and already-absolute values pass through unchanged. An optional second argument overrides the configured base for this call — e.g. a value read from framework runtime config. */')\n lines.push('export declare function media(value: string, baseOverride?: string): string')\n lines.push('/** Resolve every `](media/...)` reference inside a markdown string to an absolute delivery URL. Same override rules as `media()`. */')\n lines.push('export declare function mediaBody(markdown: string, baseOverride?: string): string')\n lines.push('')\n }\n\n // Typed client interface\n lines.push('export interface ContentrainClient {')\n for (const m of collections) {\n lines.push(` query(model: '${m.id}'): QueryBuilder<${kebabToPascal(m.id)}>`)\n }\n lines.push(' query(model: string): QueryBuilder<Record<string, unknown>>')\n for (const m of singletons) {\n lines.push(` singleton(model: '${m.id}'): SingletonAccessor<${kebabToPascal(m.id)}>`)\n }\n lines.push(' singleton(model: string): SingletonAccessor<Record<string, unknown>>')\n for (const m of dictionaries) {\n lines.push(` dictionary(model: '${m.id}'): DictionaryAccessor`)\n }\n lines.push(' dictionary(model: string): DictionaryAccessor')\n for (const m of documents) {\n lines.push(` document(model: '${m.id}'): DocumentQuery<${kebabToPascal(m.id)}>`)\n }\n lines.push(' document(model: string): DocumentQuery<Record<string, unknown>>')\n if (hasMedia) {\n lines.push(' media(value: string, baseOverride?: string): string')\n lines.push(' mediaBody(markdown: string, baseOverride?: string): string')\n }\n lines.push('}')\n lines.push('')\n lines.push('export declare function createContentrainClient(): ContentrainClient')\n lines.push('')\n\n return lines.join('\\n') + '\\n'\n}\n\nfunction fieldToTS(field: FieldDef): string {\n switch (field.type) {\n case 'string': case 'text': case 'email': case 'url': case 'slug':\n case 'color': case 'phone': case 'code': case 'icon':\n case 'markdown': case 'richtext':\n case 'date': case 'datetime':\n case 'image': case 'video': case 'file':\n case 'relation': {\n if (Array.isArray(field.model) && field.model.length > 1) {\n const union = field.model.map(m => `'${m}'`).join(' | ')\n return `{ model: ${union}; ref: string }`\n }\n // Single-target relation: stored as an id string, but include() resolves\n // it to the target entry, so the field can be either at read time.\n const target = Array.isArray(field.model) ? field.model[0] : field.model\n return target ? `string | ${kebabToPascal(target)}` : 'string'\n }\n\n case 'relations': {\n if (Array.isArray(field.model) && field.model.length > 1) {\n const union = field.model.map(m => `'${m}'`).join(' | ')\n return `Array<{ model: ${union}; ref: string }>`\n }\n const target = Array.isArray(field.model) ? field.model[0] : field.model\n return target ? `Array<string | ${kebabToPascal(target)}>` : 'string[]'\n }\n\n case 'number': case 'integer': case 'decimal': case 'percent': case 'rating':\n return 'number'\n\n case 'boolean':\n return 'boolean'\n\n case 'select':\n if (field.options && field.options.length > 0) {\n return field.options.map(o => `'${o}'`).join(' | ')\n }\n return 'string'\n\n case 'array':\n if (field.items) {\n if (typeof field.items === 'string') {\n return `${primitiveToTS(field.items)}[]`\n }\n // items is a FieldDef\n return `${fieldToTS(field.items)}[]`\n }\n return 'unknown[]'\n\n case 'object':\n if (field.fields) {\n const entries = Object.entries(field.fields)\n .map(([k, v]) => `${k}${v.required ? '' : '?'}: ${fieldToTS(v)}`)\n .join('; ')\n return `{ ${entries} }`\n }\n return 'Record<string, unknown>'\n\n default:\n return 'unknown'\n }\n}\n\nfunction primitiveToTS(type: string): string {\n if (['string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext', 'date', 'datetime', 'image', 'video', 'file', 'relation'].includes(type)) {\n return 'string'\n }\n if (['number', 'integer', 'decimal', 'percent', 'rating'].includes(type)) {\n return 'number'\n }\n if (type === 'boolean') return 'boolean'\n return 'unknown'\n}\n\nfunction kebabToPascal(s: string): string {\n return s.split('-').map(part => part.charAt(0).toUpperCase() + part.slice(1)).join('')\n}\n","import { publicationMeta, isPublishedAt, type PublicationContext } from './publication.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport type { ContentFileRef } from './config-reader.js'\nimport { parseFrontmatter, stringLikeFieldKeys } from '../shared/frontmatter.js'\nimport { readJson, readText } from './utils.js'\n\n/** Deterministic JSON: sorted keys, 2-space indent, trailing newline */\nfunction canonicalStringify(data: unknown): string {\n return JSON.stringify(data, (_, v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.keys(v).toSorted().reduce<Record<string, unknown>>((acc, k) => {\n acc[k] = (v as Record<string, unknown>)[k]\n return acc\n }, {})\n }\n return v\n }, 2)\n}\n\nexport interface DataModule {\n fileName: string\n content: string\n}\n\nexport async function emitDataModules(\n models: ModelDefinition[],\n contentFiles: ContentFileRef[],\n publication?: PublicationContext,\n): Promise<DataModule[]> {\n const results = await Promise.all(\n contentFiles.map(ref => emitSingleModule(ref, models, publication)),\n )\n return results.filter((r): r is DataModule => r !== null)\n}\n\nasync function emitSingleModule(\n ref: ContentFileRef,\n models: ModelDefinition[],\n publication?: PublicationContext,\n): Promise<DataModule | null> {\n const model = models.find(m => m.id === ref.modelId)\n if (!model) return null\n\n const meta = publication ? await publicationMeta(ref, model, publication) : undefined\n const visible = (id?: string) => !publication || isPublishedAt(id === undefined ? meta : meta?.[id], publication.at, publication.requireStatus)\n // Dictionary meta is one record for the whole file, as for a singleton or document.\n if (model.kind !== 'collection' && !visible()) return null\n\n const localeSuffix = ref.locale ? `.${ref.locale}` : ''\n\n switch (model.kind) {\n case 'collection': {\n const raw = await readJson<Record<string, Record<string, unknown>>>(ref.filePath)\n if (!raw) return null\n const entries = Object.entries(raw)\n .filter(([id]) => visible(id))\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([id, fields]) => Object.assign({ id }, fields))\n return { fileName: `${model.id}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(entries)}\\n` }\n }\n\n case 'singleton': {\n const raw = await readJson<Record<string, unknown>>(ref.filePath)\n if (!raw) return null\n return { fileName: `${model.id}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(raw)}\\n` }\n }\n\n case 'dictionary': {\n const raw = await readJson<Record<string, string>>(ref.filePath)\n if (!raw) return null\n return { fileName: `${model.id}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(raw)}\\n` }\n }\n\n case 'document': {\n const rawText = await readText(ref.filePath)\n if (!rawText) return null\n const { frontmatter, body } = parseFrontmatter(rawText, stringLikeFieldKeys(model))\n const slug = ref.slug ?? model.id\n // Canonical document field is `body` (matches @contentrain/types\n // DocumentEntry.body and the MCP document_save schema).\n const data = { slug, ...frontmatter, body }\n return { fileName: `${model.id}--${slug}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(data)}\\n` }\n }\n\n default:\n return null\n }\n}\n","import type { ModelDefinition } from '@contentrain/types'\nimport type { DataModule } from './data-emitter.js'\n\nexport function emitRuntimeModule(models: ModelDefinition[], dataModules: DataModule[], defaultLocale?: string, mediaBaseUrl?: string): string {\n const lines: string[] = [\n '/* eslint-disable */',\n '/* oxlint-disable */',\n '// Auto-generated by @contentrain/query — do not edit manually',\n '',\n ]\n\n // Emit default locale constant if provided\n if (defaultLocale) {\n lines.push(`const _defaultLocale = '${defaultLocale}'`)\n lines.push('')\n }\n\n // Emit the media delivery base constant if one was configured. The base is\n // trimmed of trailing slashes here so the resolvers below are a plain join.\n if (mediaBaseUrl) {\n lines.push(`const _mediaBase = ${JSON.stringify(mediaBaseUrl.replace(/\\/+$/, ''))}`)\n lines.push('')\n }\n\n // Import data modules\n for (const dm of dataModules) {\n const varName = fileNameToVar(dm.fileName)\n lines.push(`import ${varName} from './data/${dm.fileName}'`)\n }\n lines.push('')\n\n // Inline minimal runtime classes\n lines.push(RUNTIME_CODE)\n lines.push('')\n\n // Build registry\n lines.push('// ─── Data Registry ───')\n lines.push('')\n\n // Group data modules by modelId\n const modelDataMap = new Map<string, { varName: string; locale: string | null }[]>()\n for (const dm of dataModules) {\n const { modelId, locale } = parseDataFileName(dm.fileName)\n if (!modelDataMap.has(modelId)) modelDataMap.set(modelId, [])\n modelDataMap.get(modelId)!.push({ varName: fileNameToVar(dm.fileName), locale })\n }\n\n // Create factory functions for each kind\n const collections = models.filter(m => m.kind === 'collection')\n const singletons = models.filter(m => m.kind === 'singleton')\n const dictionaries = models.filter(m => m.kind === 'dictionary')\n const documents = models.filter(m => m.kind === 'document')\n\n // Generate relation metadata for models that have relation fields\n const relationMetaMap = buildRelationMetaMap(models)\n\n // Generate _resolveEntry helper (needed if any model has relations)\n if (relationMetaMap.size > 0) {\n lines.push('// ─── Relation Resolver ───')\n lines.push('')\n lines.push('function _resolveEntry(model, id, locale) {')\n // Try the requested locale first, then fall back to the locale-agnostic\n // '_default' key. i18n:true targets are keyed by locale string ('en'),\n // i18n:false targets are keyed '_default' — this fallback resolves both\n // regardless of the source query's active locale.\n lines.push(' const localeKeys = locale ? [locale, \\'_default\\'] : [\\'_default\\']')\n lines.push(' for (const localeKey of localeKeys) {')\n // Search in collection registry\n if (collections.length > 0) {\n lines.push(' const colData = _collectionRegistry[model]?.get(localeKey)')\n lines.push(' if (colData) { const e = colData.find(x => x.id === id); if (e) return e; }')\n }\n // Search in document registry\n if (documents.length > 0) {\n lines.push(' const docData = _documentRegistry[model]?.get(localeKey)')\n lines.push(' if (docData) { const e = docData.find(x => x.slug === id); if (e) return e; }')\n }\n lines.push(' }')\n lines.push(' return undefined')\n lines.push('}')\n lines.push('')\n\n // Emit relation meta objects\n lines.push('const _relationMeta = {')\n for (const [modelId, meta] of relationMetaMap) {\n lines.push(` '${modelId}': {`)\n for (const [field, info] of Object.entries(meta)) {\n const targetStr = Array.isArray(info.target)\n ? `[${info.target.map(t => `'${t}'`).join(', ')}]`\n : `'${info.target}'`\n lines.push(` '${field}': { target: ${targetStr}, multi: ${info.multi} },`)\n }\n lines.push(' },')\n }\n lines.push('}')\n lines.push('')\n }\n\n // query() function\n if (collections.length > 0) {\n lines.push('const _collectionRegistry = {')\n for (const m of collections) {\n const entries = modelDataMap.get(m.id) ?? []\n lines.push(` '${m.id}': new Map([`)\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n lines.push(` ['${localeKey}', ${e.varName}],`)\n }\n lines.push(' ]),')\n }\n lines.push('}')\n lines.push('')\n lines.push('export function query(model) {')\n lines.push(' const data = _collectionRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown collection model: \"${model}\"`)')\n if (relationMetaMap.size > 0 && defaultLocale) {\n lines.push(' return new QueryBuilder(data, _relationMeta[model], _resolveEntry, _defaultLocale)')\n } else if (relationMetaMap.size > 0) {\n lines.push(' return new QueryBuilder(data, _relationMeta[model], _resolveEntry)')\n } else if (defaultLocale) {\n lines.push(' return new QueryBuilder(data, undefined, undefined, _defaultLocale)')\n } else {\n lines.push(' return new QueryBuilder(data)')\n }\n lines.push('}')\n lines.push('')\n }\n\n // singleton() function\n if (singletons.length > 0) {\n lines.push('const _singletonRegistry = {')\n for (const m of singletons) {\n const entries = modelDataMap.get(m.id) ?? []\n lines.push(` '${m.id}': new Map([`)\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n lines.push(` ['${localeKey}', ${e.varName}],`)\n }\n lines.push(' ]),')\n }\n lines.push('}')\n lines.push('')\n lines.push('export function singleton(model) {')\n lines.push(' const data = _singletonRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown singleton model: \"${model}\"`)')\n if (relationMetaMap.size > 0 && defaultLocale) {\n lines.push(' return new SingletonAccessor(data, _defaultLocale, _relationMeta[model], _resolveEntry)')\n } else if (relationMetaMap.size > 0) {\n lines.push(' return new SingletonAccessor(data, undefined, _relationMeta[model], _resolveEntry)')\n } else if (defaultLocale) {\n lines.push(' return new SingletonAccessor(data, _defaultLocale)')\n } else {\n lines.push(' return new SingletonAccessor(data)')\n }\n lines.push('}')\n lines.push('')\n }\n\n // dictionary() function\n if (dictionaries.length > 0) {\n lines.push('const _dictionaryRegistry = {')\n for (const m of dictionaries) {\n const entries = modelDataMap.get(m.id) ?? []\n lines.push(` '${m.id}': new Map([`)\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n lines.push(` ['${localeKey}', ${e.varName}],`)\n }\n lines.push(' ]),')\n }\n lines.push('}')\n lines.push('')\n lines.push('export function dictionary(model) {')\n lines.push(' const data = _dictionaryRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown dictionary model: \"${model}\"`)')\n lines.push(defaultLocale ? ' return new DictionaryAccessor(data, _defaultLocale)' : ' return new DictionaryAccessor(data)')\n lines.push('}')\n lines.push('')\n }\n\n // document() function\n if (documents.length > 0) {\n // Group document data modules by model+locale and merge into arrays\n for (const m of documents) {\n const entries = modelDataMap.get(m.id) ?? []\n // Group entries by locale\n const byLocale = new Map<string, string[]>()\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n if (!byLocale.has(localeKey)) byLocale.set(localeKey, [])\n byLocale.get(localeKey)!.push(e.varName)\n }\n lines.push(`const _doc_${m.id.replace(/-/g, '_')} = new Map([`)\n for (const [localeKey, varNames] of byLocale) {\n lines.push(` ['${localeKey}', [${varNames.join(', ')}]],`)\n }\n lines.push('])')\n }\n lines.push('')\n lines.push('const _documentRegistry = {')\n for (const m of documents) {\n lines.push(` '${m.id}': _doc_${m.id.replace(/-/g, '_')},`)\n }\n lines.push('}')\n lines.push('')\n lines.push('export function document(model) {')\n lines.push(' const data = _documentRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown document model: \"${model}\"`)')\n if (relationMetaMap.size > 0 && defaultLocale) {\n lines.push(' return new DocumentQuery(data, _relationMeta[model], _resolveEntry, _defaultLocale)')\n } else if (relationMetaMap.size > 0) {\n lines.push(' return new DocumentQuery(data, _relationMeta[model], _resolveEntry)')\n } else if (defaultLocale) {\n lines.push(' return new DocumentQuery(data, undefined, undefined, _defaultLocale)')\n } else {\n lines.push(' return new DocumentQuery(data)')\n }\n lines.push('}')\n lines.push('')\n }\n\n // media()/mediaBody() resolvers — only when a media base is configured.\n // Mirror the rewrite rules of the write path: a stored `media/...` path (or\n // reference inside markdown, for mediaBody) becomes an absolute delivery\n // URL; external URLs and already-absolute values pass through, so both are\n // safe to call on any value (idempotent). Each takes an optional per-call\n // base override so a host that reads its own runtime config — a Nuxt app's\n // `useRuntimeConfig()`, for one — is not stuck with the base baked in at\n // generate time.\n if (mediaBaseUrl) {\n lines.push('// ─── Media ───')\n lines.push('')\n lines.push('export function media(value, baseOverride) {')\n lines.push(' if (typeof value !== \\'string\\' || !value.startsWith(\\'media/\\')) return value')\n lines.push(' var base = baseOverride ? String(baseOverride).replace(/\\\\/+$/, \\'\\') : _mediaBase')\n lines.push(' return base + \\'/\\' + value')\n lines.push('}')\n lines.push('')\n lines.push('export function mediaBody(markdown, baseOverride) {')\n lines.push(' if (typeof markdown !== \\'string\\') return markdown')\n lines.push(' var base = baseOverride ? String(baseOverride).replace(/\\\\/+$/, \\'\\') : _mediaBase')\n lines.push(' return markdown.replace(/\\\\]\\\\((media\\\\/[^)\\\\s]+)\\\\)/g, function (_m, p) { return \\'](\\' + base + \\'/\\' + p + \\')\\' })')\n lines.push('}')\n lines.push('')\n }\n\n return lines.join('\\n') + '\\n'\n}\n\nexport function emitCjsWrapper(models: ModelDefinition[], hasMedia = false): string {\n const exports: string[] = []\n if (models.some(m => m.kind === 'collection')) exports.push('query')\n if (models.some(m => m.kind === 'singleton')) exports.push('singleton')\n if (models.some(m => m.kind === 'dictionary')) exports.push('dictionary')\n if (models.some(m => m.kind === 'document')) exports.push('document')\n if (hasMedia) exports.push('media', 'mediaBody')\n\n return `/* eslint-disable */\n/* oxlint-disable */\n// Auto-generated CJS proxy — delegates to ESM via dynamic import()\n// The generated client is ESM-first. From CommonJS, await init() once before\n// accessing exports:\n// const client = await require('#contentrain').init()\n// client.query('model')\n// Prefer native ESM (import) where possible.\n'use strict'\nlet _mod = null\nlet _promise = null\n\nmodule.exports.init = function() {\n if (!_promise) _promise = import('./index.mjs').then(function(m) {\n _mod = m\n${exports.map(e => ` module.exports.${e} = m.${e}`).join('\\n')}\n return module.exports\n })\n return _promise\n}\n\n// Eagerly start loading so subsequent sync calls work after first await\n_promise = import('./index.mjs').then(function(m) {\n _mod = m\n${exports.map(e => ` module.exports.${e} = m.${e}`).join('\\n')}\n return module.exports\n}).catch(function() { _promise = null; /* retry on next init() call */ })\n`\n}\n\nfunction buildRelationMetaMap(\n models: ModelDefinition[],\n): Map<string, Record<string, { target: string | string[]; multi: boolean }>> {\n const result = new Map<string, Record<string, { target: string | string[]; multi: boolean }>>()\n\n for (const model of models) {\n if (!model.fields) continue\n const meta: Record<string, { target: string | string[]; multi: boolean }> = {}\n let hasRelations = false\n\n for (const [fieldName, field] of Object.entries(model.fields)) {\n if (field.type === 'relation' && field.model) {\n meta[fieldName] = { target: field.model, multi: false }\n hasRelations = true\n } else if (field.type === 'relations' && field.model) {\n meta[fieldName] = { target: field.model, multi: true }\n hasRelations = true\n }\n }\n\n if (hasRelations) {\n result.set(model.id, meta)\n }\n }\n\n return result\n}\n\nfunction fileNameToVar(fileName: string): string {\n // blog-post.en.mjs → _blogPostEn\n const base = fileName.replace('.mjs', '')\n const parts = base.split(/[-.]/)\n return '_' + parts.map((p, i) => i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)).join('')\n}\n\nfunction parseDataFileName(fileName: string): { modelId: string; locale: string | null } {\n const base = fileName.replace('.mjs', '')\n\n // Document files use \"modelId--slug.locale\" or \"modelId--slug\" format\n // Extract modelId from before \"--\", ignore slug part\n let nameForLocale = base\n if (base.includes('--')) {\n nameForLocale = base // locale is still the last dot-segment\n }\n\n // Extract locale from last dot-segment (e.g., \"blog-post.en\" → locale \"en\")\n const parts = nameForLocale.split('.')\n let locale: string | null = null\n let nameWithoutLocale = nameForLocale\n if (parts.length >= 2) {\n const possibleLocale = parts[parts.length - 1]!\n if (possibleLocale.length === 2 || possibleLocale.includes('-')) {\n locale = possibleLocale\n nameWithoutLocale = parts.slice(0, -1).join('.')\n }\n }\n\n // Extract modelId: if \"--\" present, it's the part before \"--\"\n const modelId = nameWithoutLocale.includes('--')\n ? nameWithoutLocale.split('--')[0]!\n : nameWithoutLocale\n\n return { modelId, locale }\n}\n\n// Inlined runtime code — zero dependencies\nconst RUNTIME_CODE = `\n// ─── Runtime Helpers ───\n\nfunction _applyWhere(item, clause) {\n const val = item[clause.field];\n switch (clause.op) {\n case 'eq': return Array.isArray(val) ? val.includes(clause.value) : val === clause.value;\n case 'ne': return Array.isArray(val) ? !val.includes(clause.value) : val !== clause.value;\n case 'gt': return val > clause.value;\n case 'gte': return val >= clause.value;\n case 'lt': return val < clause.value;\n case 'lte': return val <= clause.value;\n case 'in': return Array.isArray(clause.value) && clause.value.includes(val);\n case 'contains': {\n if (typeof val === 'string') return val.includes(clause.value);\n if (Array.isArray(val)) return val.includes(clause.value);\n return false;\n }\n default: return true;\n }\n}\n\n// ─── Runtime Classes ───\n\nclass QueryBuilder {\n constructor(data, relationMeta, resolver, defaultLocale) { this._data = data; this._filters = []; this._sortField = null; this._sortOrder = 'asc'; this._limit = null; this._offset = 0; this._locale = null; this._includes = []; this._relationMeta = relationMeta || {}; this._resolver = resolver || null; this._defaultLocale = defaultLocale || null; }\n locale(lang) { this._locale = lang; return this; }\n where(field, opOrValue, value) { if (value !== undefined) { this._filters.push({ field, op: opOrValue, value }); } else { this._filters.push({ field, op: 'eq', value: opOrValue }); } return this; }\n sort(field, order = 'asc') { this._sortField = field; this._sortOrder = order; return this; }\n limit(n) { this._limit = n; return this; }\n offset(n) { this._offset = n; return this; }\n include(...fields) { this._includes.push(...fields); return this; }\n count() { return this.all().length; }\n all() {\n let items; if (this._locale) { items = [...(this._data.get(this._locale) ?? [])]; } else if (this._defaultLocale && this._data.has(this._defaultLocale)) { items = [...this._data.get(this._defaultLocale)]; } else { items = [...(this._data.get(this._data.keys().next().value) ?? [])]; }\n for (const clause of this._filters) items = items.filter(item => _applyWhere(item, clause));\n if (this._sortField) { const sf = this._sortField; const d = this._sortOrder === 'asc' ? 1 : -1; items.sort((a, b) => { const va = a[sf], vb = b[sf]; if (va == null && vb == null) return 0; if (va == null) return d; if (vb == null) return -d; return va < vb ? -d : va > vb ? d : 0; }); }\n if (this._offset > 0 || this._limit !== null) { const end = this._limit !== null ? this._offset + this._limit : undefined; items = items.slice(this._offset, end); }\n if (this._includes.length > 0 && this._resolver) { items = items.map(item => this._resolveIncludes(item)); }\n return items;\n }\n first() { return this.all()[0]; }\n _resolveIncludes(item) {\n const resolved = { ...item };\n const _loc = this._locale ?? this._defaultLocale;\n for (const field of this._includes) {\n const meta = this._relationMeta[field]; if (!meta) continue;\n const targets = Array.isArray(meta.target) ? meta.target : [meta.target];\n if (meta.multi) { const ids = item[field]; if (Array.isArray(ids)) { resolved[field] = ids.map(id => { if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) return r; } return id; } if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) return r; } return id; }); } }\n else { const id = item[field]; if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) { resolved[field] = r; break; } } } else if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) resolved[field] = r; } }\n }\n return resolved;\n }\n}\n\nclass SingletonAccessor {\n constructor(data, defaultLocale, relationMeta, resolveEntry) { this._data = data; this._locale = null; this._defaultLocale = defaultLocale || null; this._includes = []; this._relationMeta = relationMeta || {}; this._resolver = resolveEntry; }\n locale(lang) { this._locale = lang; return this; }\n include(...fields) { this._includes.push(...fields); return this; }\n get() {\n const locale = this._locale || this._defaultLocale;\n let d = locale ? this._data.get(locale) : undefined;\n if (!d) { const key = this._data.keys().next().value; d = key !== undefined ? this._data.get(key) : undefined; }\n if (!d) throw new Error('No data available');\n if (this._includes.length > 0 && this._resolver) return this._resolveIncludes(d, locale || 'en');\n return d;\n }\n _resolveIncludes(item, locale) {\n const resolved = { ...item };\n for (const field of this._includes) {\n const meta = this._relationMeta[field]; if (!meta) continue;\n const targets = Array.isArray(meta.target) ? meta.target : [meta.target];\n if (meta.multi) { const ids = item[field]; if (Array.isArray(ids)) { resolved[field] = ids.map(id => { if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, locale); if (r) return r; } return id; } if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, locale); if (r) return r; } return id; }); } }\n else { const id = item[field]; if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, locale); if (r) { resolved[field] = r; break; } } } else if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, locale); if (r) resolved[field] = r; } }\n }\n return resolved;\n }\n}\n\nclass DictionaryAccessor {\n constructor(data, defaultLocale) { this._data = data; this._locale = null; this._defaultLocale = defaultLocale || null; }\n locale(lang) { this._locale = lang; return this; }\n get(key, params) {\n let dict; if (this._locale) { dict = this._data.get(this._locale) ?? {}; } else if (this._defaultLocale && this._data.has(this._defaultLocale)) { dict = this._data.get(this._defaultLocale); } else { const loc = this._data.keys().next().value; dict = this._data.get(loc) ?? {}; }\n if (key === undefined) return dict;\n const val = dict[key];\n if (val === undefined) return undefined;\n if (params) return val.replace(/\\\\{(\\\\w+)\\\\}/g, (m, k) => { const v = params[k]; return v !== undefined ? String(v) : m; });\n return val;\n }\n}\n\nclass DocumentQuery {\n constructor(data, relationMeta, resolver, defaultLocale) { this._data = data; this._filters = []; this._sortField = null; this._sortOrder = 'asc'; this._locale = null; this._includes = []; this._relationMeta = relationMeta || {}; this._resolver = resolver || null; this._defaultLocale = defaultLocale || null; }\n locale(lang) { this._locale = lang; return this; }\n where(field, opOrValue, value) { if (value !== undefined) { this._filters.push({ field, op: opOrValue, value }); } else { this._filters.push({ field, op: 'eq', value: opOrValue }); } return this; }\n sort(field, order = 'asc') { this._sortField = field; this._sortOrder = order; return this; }\n include(...fields) { this._includes.push(...fields); return this; }\n bySlug(slug) {\n const items = this._resolveData(); const item = items.find(x => x.slug === slug);\n if (item && this._includes.length > 0 && this._resolver) return this._resolveIncludes(item);\n return item;\n }\n count() { return this.all().length; }\n first() { return this.all()[0]; }\n all() { let items = this._resolveData(); for (const clause of this._filters) items = items.filter(item => _applyWhere(item, clause)); if (this._sortField) { const sf = this._sortField; const d = this._sortOrder === 'asc' ? 1 : -1; items.sort((a, b) => { const va = a[sf], vb = b[sf]; if (va == null && vb == null) return 0; if (va == null) return d; if (vb == null) return -d; return va < vb ? -d : va > vb ? d : 0; }); } if (this._includes.length > 0 && this._resolver) { items = items.map(item => this._resolveIncludes(item)); } return items; }\n _resolveData() { let key; if (this._locale) { key = this._locale; } else if (this._defaultLocale && this._data.has(this._defaultLocale)) { key = this._defaultLocale; } else { key = this._data.keys().next().value; } return [...(this._data.get(key) ?? [])]; }\n _resolveIncludes(item) {\n const resolved = { ...item };\n const _loc = this._locale ?? this._defaultLocale;\n for (const field of this._includes) {\n const meta = this._relationMeta[field]; if (!meta) continue;\n const targets = Array.isArray(meta.target) ? meta.target : [meta.target];\n if (meta.multi) { const ids = item[field]; if (Array.isArray(ids)) { resolved[field] = ids.map(id => { if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) return r; } return id; } if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) return r; } return id; }); } }\n else { const id = item[field]; if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) { resolved[field] = r; break; } } } else if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) resolved[field] = r; } }\n }\n return resolved;\n }\n}\n`.trim()\n","import { join } from 'node:path'\nimport { readJson, writeText } from './utils.js'\n\nconst IMPORTS_CONFIG = {\n '#contentrain': {\n types: './.contentrain/client/index.d.ts',\n import: './.contentrain/client/index.mjs',\n require: './.contentrain/client/index.cjs',\n default: './.contentrain/client/index.mjs',\n },\n '#contentrain/*': {\n types: './.contentrain/client/*.d.ts',\n import: './.contentrain/client/*.mjs',\n require: './.contentrain/client/*.cjs',\n default: './.contentrain/client/*.mjs',\n },\n}\n\nexport async function injectImports(projectRoot: string): Promise<boolean> {\n const pkgPath = join(projectRoot, 'package.json')\n const pkg = await readJson<Record<string, unknown>>(pkgPath)\n if (!pkg) return false\n\n const existing = (pkg['imports'] as Record<string, unknown>) ?? {}\n const updated = { ...existing, ...IMPORTS_CONFIG }\n\n // Check if already matches\n if (JSON.stringify(existing['#contentrain']) === JSON.stringify(IMPORTS_CONFIG['#contentrain']) &&\n JSON.stringify(existing['#contentrain/*']) === JSON.stringify(IMPORTS_CONFIG['#contentrain/*'])) {\n return false // No change needed\n }\n\n pkg['imports'] = updated\n await writeText(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n return true\n}\n","import { publicationContext, type PublicationOptions } from './publication.js'\nimport { join } from 'node:path'\nimport { rm } from 'node:fs/promises'\nimport { readProjectManifest } from './config-reader.js'\nimport { emitTypes } from './type-emitter.js'\nimport { emitDataModules } from './data-emitter.js'\nimport { emitRuntimeModule, emitCjsWrapper } from './runtime-emitter.js'\nimport { injectImports } from './package-json.js'\nimport { readDir, writeText } from './utils.js'\n\nexport interface GenerateOptions extends PublicationOptions {\n projectRoot: string\n /**\n * Public media delivery base for resolving relative `media/...` references\n * (in image/video/file fields, and `](media/...)` links in markdown) to\n * absolute URLs. When set (or when `config.cdn.url` is present), the\n * emitted client gains `media()`/`mediaBody()` resolvers. The explicit\n * option wins over the config value. Omit for the pure local-file model —\n * media stays a relative path until a consumer resolves it itself, e.g.\n * via `resolveMediaUrl()`/`resolveMediaRefsInBody()` with a base sourced\n * from its own env or runtime config (a Nuxt app's `useRuntimeConfig()`).\n */\n mediaBaseUrl?: string\n /** @deprecated Alias for `mediaBaseUrl`, kept for existing callers. */\n cdnBaseUrl?: string\n}\n\nexport interface GenerateResult {\n generatedFiles: string[]\n typesCount: number\n dataModulesCount: number\n packageJsonUpdated: boolean\n}\n\nexport async function generate(options: GenerateOptions): Promise<GenerateResult> {\n const { projectRoot } = options\n const clientDir = join(projectRoot, '.contentrain', 'client')\n const dataDir = join(clientDir, 'data')\n\n // 1. Read project manifest\n const manifest = await readProjectManifest(projectRoot)\n\n // Media delivery base: explicit option wins over config.cdn.url. When set,\n // the emitted client gains `media()`/`mediaBody()` resolvers.\n const mediaBaseUrl = options.mediaBaseUrl ?? options.cdnBaseUrl ?? manifest.config.cdn?.url\n const hasMedia = Boolean(mediaBaseUrl)\n\n // 2. Generate data modules (async — reads content files)\n const dataModules = await emitDataModules(manifest.models, manifest.contentFiles, publicationContext(projectRoot, manifest.config.locales.default, options))\n\n // 3. Generate all output content (sync — pure string transforms)\n const typesContent = emitTypes(manifest.models, hasMedia)\n const runtimeContent = emitRuntimeModule(manifest.models, dataModules, manifest.config.locales.default, mediaBaseUrl)\n const cjsContent = emitCjsWrapper(manifest.models, hasMedia)\n\n // 4. Clean stale data modules\n const newFileNames = new Set(dataModules.map(dm => dm.fileName))\n try {\n const existing = await readDir(dataDir)\n await Promise.all(\n existing\n .filter(f => !newFileNames.has(f))\n .map(f => rm(join(dataDir, f), { force: true })),\n )\n } catch { /* dataDir may not exist yet */ }\n\n // 5. Write all files in parallel\n const dataFileNames = dataModules.map(dm => `data/${dm.fileName}`)\n await Promise.all([\n writeText(join(clientDir, 'index.d.ts'), typesContent),\n writeText(join(clientDir, 'index.mjs'), runtimeContent),\n writeText(join(clientDir, 'index.cjs'), cjsContent),\n ...dataModules.map(dm => writeText(join(dataDir, dm.fileName), dm.content)),\n ])\n\n // 6. Inject #imports into package.json\n const packageJsonUpdated = await injectImports(projectRoot)\n\n return {\n generatedFiles: ['index.d.ts', ...dataFileNames, 'index.mjs', 'index.cjs'],\n typesCount: manifest.models.length,\n dataModulesCount: dataModules.length,\n packageJsonUpdated,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generate-1RkU4u-9.mjs","names":[],"sources":["../src/generator/type-emitter.ts","../src/generator/data-emitter.ts","../src/generator/runtime-emitter.ts","../src/generator/package-json.ts","../src/generator/generate.ts"],"sourcesContent":["import type { ModelDefinition, FieldDef } from '@contentrain/types'\n\nexport function emitTypes(models: ModelDefinition[], hasMedia = false): string {\n const lines: string[] = [\n '/* eslint-disable */',\n '/* oxlint-disable */',\n '// Auto-generated by @contentrain/query — do not edit manually',\n '// Re-generate with: npx contentrain-query generate',\n '',\n \"export type ContentStatus = 'draft' | 'published' | 'in_review' | 'rejected' | 'archived'\",\n \"export type WhereOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains'\",\n '',\n ]\n\n // Model interfaces\n for (const model of models) {\n if (model.kind === 'dictionary') {\n lines.push(`export type ${kebabToPascal(model.id)} = Record<string, string>`)\n } else {\n lines.push(`export interface ${kebabToPascal(model.id)} {`)\n // System fields by kind. Track their names so a model-defined field of\n // the same name is not emitted twice (the base field wins).\n const baseNames = new Set<string>()\n if (model.kind === 'collection') {\n lines.push(' id: string')\n baseNames.add('id')\n }\n if (model.kind === 'document') {\n lines.push(' slug: string')\n lines.push(' body: string')\n baseNames.add('slug')\n baseNames.add('body')\n }\n // User fields\n if (model.fields) {\n for (const [name, field] of Object.entries(model.fields)) {\n if (baseNames.has(name)) continue\n const tsType = fieldToTS(field)\n const optional = field.required ? '' : '?'\n lines.push(` ${name}${optional}: ${tsType}`)\n }\n }\n lines.push('}')\n }\n lines.push('')\n }\n\n // Query interfaces\n lines.push(`export interface QueryBuilder<T> {\n locale(lang: string): QueryBuilder<T>\n where<K extends keyof T>(field: K, value: T[K]): QueryBuilder<T>\n where<K extends keyof T>(field: K, op: WhereOp, value: unknown): QueryBuilder<T>\n sort<K extends keyof T>(field: K, order?: 'asc' | 'desc'): QueryBuilder<T>\n limit(n: number): QueryBuilder<T>\n offset(n: number): QueryBuilder<T>\n include<K extends keyof T & string>(...fields: K[]): QueryBuilder<T>\n count(): number\n first(): T | undefined\n all(): T[]\n}`)\n lines.push('')\n\n lines.push(`export interface SingletonAccessor<T> {\n locale(lang: string): SingletonAccessor<T>\n include<K extends keyof T & string>(...fields: K[]): SingletonAccessor<T>\n get(): T\n}`)\n lines.push('')\n\n lines.push(`export interface DictionaryAccessor {\n locale(lang: string): DictionaryAccessor\n get(): Record<string, string>\n get(key: string): string | undefined\n get(key: string, params: Record<string, string | number>): string\n}`)\n lines.push('')\n\n lines.push(`export interface DocumentQuery<T> {\n locale(lang: string): DocumentQuery<T>\n where<K extends keyof T>(field: K, value: T[K]): DocumentQuery<T>\n where<K extends keyof T>(field: K, op: WhereOp, value: unknown): DocumentQuery<T>\n sort<K extends keyof T>(field: K, order?: 'asc' | 'desc'): DocumentQuery<T>\n include<K extends keyof T & string>(...fields: K[]): DocumentQuery<T>\n bySlug(slug: string): T | undefined\n count(): number\n first(): T | undefined\n all(): T[]\n}`)\n lines.push('')\n\n // Overloaded entry points\n const collections = models.filter(m => m.kind === 'collection')\n const singletons = models.filter(m => m.kind === 'singleton')\n const dictionaries = models.filter(m => m.kind === 'dictionary')\n const documents = models.filter(m => m.kind === 'document')\n\n // query() overloads\n for (const m of collections) {\n lines.push(`export declare function query(model: '${m.id}'): QueryBuilder<${kebabToPascal(m.id)}>`)\n }\n lines.push('export declare function query(model: string): QueryBuilder<Record<string, unknown>>')\n lines.push('')\n\n // singleton() overloads\n for (const m of singletons) {\n lines.push(`export declare function singleton(model: '${m.id}'): SingletonAccessor<${kebabToPascal(m.id)}>`)\n }\n lines.push('export declare function singleton(model: string): SingletonAccessor<Record<string, unknown>>')\n lines.push('')\n\n // dictionary() overloads\n for (const m of dictionaries) {\n lines.push(`export declare function dictionary(model: '${m.id}'): DictionaryAccessor`)\n }\n lines.push('export declare function dictionary(model: string): DictionaryAccessor')\n lines.push('')\n\n // document() overloads\n for (const m of documents) {\n lines.push(`export declare function document(model: '${m.id}'): DocumentQuery<${kebabToPascal(m.id)}>`)\n }\n lines.push('export declare function document(model: string): DocumentQuery<Record<string, unknown>>')\n lines.push('')\n\n // media()/mediaBody() resolvers — emitted only when a media delivery base\n // is configured.\n if (hasMedia) {\n lines.push('/** Resolve a stored `media/...` path to its absolute delivery URL. External URLs and already-absolute values pass through unchanged. An optional second argument overrides the configured base for this call — e.g. a value read from framework runtime config. */')\n lines.push('export declare function media(value: string, baseOverride?: string): string')\n lines.push('/** Resolve every `](media/...)` reference inside a markdown string to an absolute delivery URL. Same override rules as `media()`. */')\n lines.push('export declare function mediaBody(markdown: string, baseOverride?: string): string')\n lines.push('')\n }\n\n // Typed client interface\n lines.push('export interface ContentrainClient {')\n for (const m of collections) {\n lines.push(` query(model: '${m.id}'): QueryBuilder<${kebabToPascal(m.id)}>`)\n }\n lines.push(' query(model: string): QueryBuilder<Record<string, unknown>>')\n for (const m of singletons) {\n lines.push(` singleton(model: '${m.id}'): SingletonAccessor<${kebabToPascal(m.id)}>`)\n }\n lines.push(' singleton(model: string): SingletonAccessor<Record<string, unknown>>')\n for (const m of dictionaries) {\n lines.push(` dictionary(model: '${m.id}'): DictionaryAccessor`)\n }\n lines.push(' dictionary(model: string): DictionaryAccessor')\n for (const m of documents) {\n lines.push(` document(model: '${m.id}'): DocumentQuery<${kebabToPascal(m.id)}>`)\n }\n lines.push(' document(model: string): DocumentQuery<Record<string, unknown>>')\n if (hasMedia) {\n lines.push(' media(value: string, baseOverride?: string): string')\n lines.push(' mediaBody(markdown: string, baseOverride?: string): string')\n }\n lines.push('}')\n lines.push('')\n lines.push('export declare function createContentrainClient(): ContentrainClient')\n lines.push('')\n\n return lines.join('\\n') + '\\n'\n}\n\nfunction fieldToTS(field: FieldDef): string {\n switch (field.type) {\n case 'string': case 'text': case 'email': case 'url': case 'slug':\n case 'color': case 'phone': case 'code': case 'icon':\n case 'markdown': case 'richtext':\n case 'date': case 'datetime':\n case 'image': case 'video': case 'file':\n case 'relation': {\n if (Array.isArray(field.model) && field.model.length > 1) {\n const union = field.model.map(m => `'${m}'`).join(' | ')\n return `{ model: ${union}; ref: string }`\n }\n // Single-target relation: stored as an id string, but include() resolves\n // it to the target entry, so the field can be either at read time.\n const target = Array.isArray(field.model) ? field.model[0] : field.model\n return target ? `string | ${kebabToPascal(target)}` : 'string'\n }\n\n case 'relations': {\n if (Array.isArray(field.model) && field.model.length > 1) {\n const union = field.model.map(m => `'${m}'`).join(' | ')\n return `Array<{ model: ${union}; ref: string }>`\n }\n const target = Array.isArray(field.model) ? field.model[0] : field.model\n return target ? `Array<string | ${kebabToPascal(target)}>` : 'string[]'\n }\n\n case 'number': case 'integer': case 'decimal': case 'percent': case 'rating':\n return 'number'\n\n case 'boolean':\n return 'boolean'\n\n case 'select':\n if (field.options && field.options.length > 0) {\n return field.options.map(o => `'${o}'`).join(' | ')\n }\n return 'string'\n\n case 'array':\n if (field.items) {\n if (typeof field.items === 'string') {\n return `${primitiveToTS(field.items)}[]`\n }\n // items is a FieldDef\n return `${fieldToTS(field.items)}[]`\n }\n return 'unknown[]'\n\n case 'object':\n if (field.fields) {\n const entries = Object.entries(field.fields)\n .map(([k, v]) => `${k}${v.required ? '' : '?'}: ${fieldToTS(v)}`)\n .join('; ')\n return `{ ${entries} }`\n }\n return 'Record<string, unknown>'\n\n default:\n return 'unknown'\n }\n}\n\nfunction primitiveToTS(type: string): string {\n if (['string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext', 'date', 'datetime', 'image', 'video', 'file', 'relation'].includes(type)) {\n return 'string'\n }\n if (['number', 'integer', 'decimal', 'percent', 'rating'].includes(type)) {\n return 'number'\n }\n if (type === 'boolean') return 'boolean'\n return 'unknown'\n}\n\nfunction kebabToPascal(s: string): string {\n return s.split('-').map(part => part.charAt(0).toUpperCase() + part.slice(1)).join('')\n}\n","import { publicationMeta, isPublishedAt, type PublicationContext } from './publication.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport type { ContentFileRef } from './config-reader.js'\nimport { parseFrontmatter, stringLikeFieldKeys } from '../shared/frontmatter.js'\nimport { readJson, readText } from './utils.js'\n\n/** Deterministic JSON: sorted keys, 2-space indent, trailing newline */\nfunction canonicalStringify(data: unknown): string {\n return JSON.stringify(data, (_, v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.keys(v).toSorted().reduce<Record<string, unknown>>((acc, k) => {\n acc[k] = (v as Record<string, unknown>)[k]\n return acc\n }, {})\n }\n return v\n }, 2)\n}\n\nexport interface DataModule {\n fileName: string\n content: string\n}\n\nexport async function emitDataModules(\n models: ModelDefinition[],\n contentFiles: ContentFileRef[],\n publication?: PublicationContext,\n): Promise<DataModule[]> {\n const results = await Promise.all(\n contentFiles.map(ref => emitSingleModule(ref, models, publication)),\n )\n return results.filter((r): r is DataModule => r !== null)\n}\n\nasync function emitSingleModule(\n ref: ContentFileRef,\n models: ModelDefinition[],\n publication?: PublicationContext,\n): Promise<DataModule | null> {\n const model = models.find(m => m.id === ref.modelId)\n if (!model) return null\n\n const meta = publication ? await publicationMeta(ref, model, publication) : undefined\n const visible = (id?: string) => !publication || isPublishedAt(id === undefined ? meta : meta?.[id], publication.at, publication.requireStatus)\n // Dictionary meta is one record for the whole file, as for a singleton or document.\n if (model.kind !== 'collection' && !visible()) return null\n\n const localeSuffix = ref.locale ? `.${ref.locale}` : ''\n\n switch (model.kind) {\n case 'collection': {\n const raw = await readJson<Record<string, Record<string, unknown>>>(ref.filePath)\n if (!raw) return null\n const entries = Object.entries(raw)\n .filter(([id]) => visible(id))\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([id, fields]) => Object.assign({ id }, fields))\n return { fileName: `${model.id}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(entries)}\\n` }\n }\n\n case 'singleton': {\n const raw = await readJson<Record<string, unknown>>(ref.filePath)\n if (!raw) return null\n return { fileName: `${model.id}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(raw)}\\n` }\n }\n\n case 'dictionary': {\n const raw = await readJson<Record<string, string>>(ref.filePath)\n if (!raw) return null\n return { fileName: `${model.id}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(raw)}\\n` }\n }\n\n case 'document': {\n const rawText = await readText(ref.filePath)\n if (!rawText) return null\n const { frontmatter, body } = parseFrontmatter(rawText, stringLikeFieldKeys(model))\n const slug = ref.slug ?? model.id\n // Canonical document field is `body` (matches @contentrain/types\n // DocumentEntry.body and the MCP document_save schema).\n const data = { slug, ...frontmatter, body }\n return { fileName: `${model.id}--${slug}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(data)}\\n` }\n }\n\n default:\n return null\n }\n}\n","import type { ModelDefinition } from '@contentrain/types'\nimport type { DataModule } from './data-emitter.js'\n\nexport function emitRuntimeModule(models: ModelDefinition[], dataModules: DataModule[], defaultLocale?: string, mediaBaseUrl?: string): string {\n const lines: string[] = [\n '/* eslint-disable */',\n '/* oxlint-disable */',\n '// Auto-generated by @contentrain/query — do not edit manually',\n '',\n ]\n\n // Emit default locale constant if provided\n if (defaultLocale) {\n lines.push(`const _defaultLocale = '${defaultLocale}'`)\n lines.push('')\n }\n\n // Emit the media delivery base constant if one was configured. The base is\n // trimmed of trailing slashes here so the resolvers below are a plain join.\n if (mediaBaseUrl) {\n lines.push(`const _mediaBase = ${JSON.stringify(mediaBaseUrl.replace(/\\/+$/, ''))}`)\n lines.push('')\n }\n\n // Import data modules\n for (const dm of dataModules) {\n const varName = fileNameToVar(dm.fileName)\n lines.push(`import ${varName} from './data/${dm.fileName}'`)\n }\n lines.push('')\n\n // Inline minimal runtime classes\n lines.push(RUNTIME_CODE)\n lines.push('')\n\n // Build registry\n lines.push('// ─── Data Registry ───')\n lines.push('')\n\n // Group data modules by modelId\n const modelDataMap = new Map<string, { varName: string; locale: string | null }[]>()\n for (const dm of dataModules) {\n const { modelId, locale } = parseDataFileName(dm.fileName)\n if (!modelDataMap.has(modelId)) modelDataMap.set(modelId, [])\n modelDataMap.get(modelId)!.push({ varName: fileNameToVar(dm.fileName), locale })\n }\n\n // Create factory functions for each kind\n const collections = models.filter(m => m.kind === 'collection')\n const singletons = models.filter(m => m.kind === 'singleton')\n const dictionaries = models.filter(m => m.kind === 'dictionary')\n const documents = models.filter(m => m.kind === 'document')\n\n // Generate relation metadata for models that have relation fields\n const relationMetaMap = buildRelationMetaMap(models)\n\n // Generate _resolveEntry helper (needed if any model has relations)\n if (relationMetaMap.size > 0) {\n lines.push('// ─── Relation Resolver ───')\n lines.push('')\n lines.push('function _resolveEntry(model, id, locale) {')\n // Try the requested locale first, then fall back to the locale-agnostic\n // '_default' key. i18n:true targets are keyed by locale string ('en'),\n // i18n:false targets are keyed '_default' — this fallback resolves both\n // regardless of the source query's active locale.\n lines.push(' const localeKeys = locale ? [locale, \\'_default\\'] : [\\'_default\\']')\n lines.push(' for (const localeKey of localeKeys) {')\n // Search in collection registry\n if (collections.length > 0) {\n lines.push(' const colData = _collectionRegistry[model]?.get(localeKey)')\n lines.push(' if (colData) { const e = colData.find(x => x.id === id); if (e) return e; }')\n }\n // Search in document registry\n if (documents.length > 0) {\n lines.push(' const docData = _documentRegistry[model]?.get(localeKey)')\n lines.push(' if (docData) { const e = docData.find(x => x.slug === id); if (e) return e; }')\n }\n lines.push(' }')\n lines.push(' return undefined')\n lines.push('}')\n lines.push('')\n\n // Emit relation meta objects\n lines.push('const _relationMeta = {')\n for (const [modelId, meta] of relationMetaMap) {\n lines.push(` '${modelId}': {`)\n for (const [field, info] of Object.entries(meta)) {\n const targetStr = Array.isArray(info.target)\n ? `[${info.target.map(t => `'${t}'`).join(', ')}]`\n : `'${info.target}'`\n lines.push(` '${field}': { target: ${targetStr}, multi: ${info.multi} },`)\n }\n lines.push(' },')\n }\n lines.push('}')\n lines.push('')\n }\n\n // query() function\n if (collections.length > 0) {\n lines.push('const _collectionRegistry = {')\n for (const m of collections) {\n const entries = modelDataMap.get(m.id) ?? []\n lines.push(` '${m.id}': new Map([`)\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n lines.push(` ['${localeKey}', ${e.varName}],`)\n }\n lines.push(' ]),')\n }\n lines.push('}')\n lines.push('')\n lines.push('export function query(model) {')\n lines.push(' const data = _collectionRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown collection model: \"${model}\"`)')\n if (relationMetaMap.size > 0 && defaultLocale) {\n lines.push(' return new QueryBuilder(data, _relationMeta[model], _resolveEntry, _defaultLocale)')\n } else if (relationMetaMap.size > 0) {\n lines.push(' return new QueryBuilder(data, _relationMeta[model], _resolveEntry)')\n } else if (defaultLocale) {\n lines.push(' return new QueryBuilder(data, undefined, undefined, _defaultLocale)')\n } else {\n lines.push(' return new QueryBuilder(data)')\n }\n lines.push('}')\n lines.push('')\n }\n\n // singleton() function\n if (singletons.length > 0) {\n lines.push('const _singletonRegistry = {')\n for (const m of singletons) {\n const entries = modelDataMap.get(m.id) ?? []\n lines.push(` '${m.id}': new Map([`)\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n lines.push(` ['${localeKey}', ${e.varName}],`)\n }\n lines.push(' ]),')\n }\n lines.push('}')\n lines.push('')\n lines.push('export function singleton(model) {')\n lines.push(' const data = _singletonRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown singleton model: \"${model}\"`)')\n if (relationMetaMap.size > 0 && defaultLocale) {\n lines.push(' return new SingletonAccessor(data, _defaultLocale, _relationMeta[model], _resolveEntry)')\n } else if (relationMetaMap.size > 0) {\n lines.push(' return new SingletonAccessor(data, undefined, _relationMeta[model], _resolveEntry)')\n } else if (defaultLocale) {\n lines.push(' return new SingletonAccessor(data, _defaultLocale)')\n } else {\n lines.push(' return new SingletonAccessor(data)')\n }\n lines.push('}')\n lines.push('')\n }\n\n // dictionary() function\n if (dictionaries.length > 0) {\n lines.push('const _dictionaryRegistry = {')\n for (const m of dictionaries) {\n const entries = modelDataMap.get(m.id) ?? []\n lines.push(` '${m.id}': new Map([`)\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n lines.push(` ['${localeKey}', ${e.varName}],`)\n }\n lines.push(' ]),')\n }\n lines.push('}')\n lines.push('')\n lines.push('export function dictionary(model) {')\n lines.push(' const data = _dictionaryRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown dictionary model: \"${model}\"`)')\n lines.push(defaultLocale ? ' return new DictionaryAccessor(data, _defaultLocale)' : ' return new DictionaryAccessor(data)')\n lines.push('}')\n lines.push('')\n }\n\n // document() function\n if (documents.length > 0) {\n // Group document data modules by model+locale and merge into arrays\n for (const m of documents) {\n const entries = modelDataMap.get(m.id) ?? []\n // Group entries by locale\n const byLocale = new Map<string, string[]>()\n for (const e of entries) {\n const localeKey = e.locale ?? '_default'\n if (!byLocale.has(localeKey)) byLocale.set(localeKey, [])\n byLocale.get(localeKey)!.push(e.varName)\n }\n lines.push(`const _doc_${m.id.replace(/-/g, '_')} = new Map([`)\n for (const [localeKey, varNames] of byLocale) {\n lines.push(` ['${localeKey}', [${varNames.join(', ')}]],`)\n }\n lines.push('])')\n }\n lines.push('')\n lines.push('const _documentRegistry = {')\n for (const m of documents) {\n lines.push(` '${m.id}': _doc_${m.id.replace(/-/g, '_')},`)\n }\n lines.push('}')\n lines.push('')\n lines.push('export function document(model) {')\n lines.push(' const data = _documentRegistry[model]')\n lines.push(' if (!data) throw new Error(`Unknown document model: \"${model}\"`)')\n if (relationMetaMap.size > 0 && defaultLocale) {\n lines.push(' return new DocumentQuery(data, _relationMeta[model], _resolveEntry, _defaultLocale)')\n } else if (relationMetaMap.size > 0) {\n lines.push(' return new DocumentQuery(data, _relationMeta[model], _resolveEntry)')\n } else if (defaultLocale) {\n lines.push(' return new DocumentQuery(data, undefined, undefined, _defaultLocale)')\n } else {\n lines.push(' return new DocumentQuery(data)')\n }\n lines.push('}')\n lines.push('')\n }\n\n // media()/mediaBody() resolvers — only when a media base is configured.\n // Mirror the rewrite rules of the write path: a stored `media/...` path (or\n // reference inside markdown, for mediaBody) becomes an absolute delivery\n // URL; external URLs and already-absolute values pass through, so both are\n // safe to call on any value (idempotent). Each takes an optional per-call\n // base override so a host that reads its own runtime config — a Nuxt app's\n // `useRuntimeConfig()`, for one — is not stuck with the base baked in at\n // generate time.\n if (mediaBaseUrl) {\n lines.push('// ─── Media ───')\n lines.push('')\n lines.push('export function media(value, baseOverride) {')\n lines.push(' if (typeof value !== \\'string\\' || !value.startsWith(\\'media/\\')) return value')\n lines.push(' var base = baseOverride ? String(baseOverride).replace(/\\\\/+$/, \\'\\') : _mediaBase')\n lines.push(' return base + \\'/\\' + value')\n lines.push('}')\n lines.push('')\n lines.push('export function mediaBody(markdown, baseOverride) {')\n lines.push(' if (typeof markdown !== \\'string\\') return markdown')\n lines.push(' var base = baseOverride ? String(baseOverride).replace(/\\\\/+$/, \\'\\') : _mediaBase')\n lines.push(' return markdown.replace(/\\\\]\\\\((media\\\\/[^)\\\\s]+)\\\\)/g, function (_m, p) { return \\'](\\' + base + \\'/\\' + p + \\')\\' })')\n lines.push('}')\n lines.push('')\n }\n\n return lines.join('\\n') + '\\n'\n}\n\nexport function emitCjsWrapper(models: ModelDefinition[], hasMedia = false): string {\n const exports: string[] = []\n if (models.some(m => m.kind === 'collection')) exports.push('query')\n if (models.some(m => m.kind === 'singleton')) exports.push('singleton')\n if (models.some(m => m.kind === 'dictionary')) exports.push('dictionary')\n if (models.some(m => m.kind === 'document')) exports.push('document')\n if (hasMedia) exports.push('media', 'mediaBody')\n\n return `/* eslint-disable */\n/* oxlint-disable */\n// Auto-generated CJS proxy — delegates to ESM via dynamic import()\n// The generated client is ESM-first. From CommonJS, await init() once before\n// accessing exports:\n// const client = await require('#contentrain').init()\n// client.query('model')\n// Prefer native ESM (import) where possible.\n'use strict'\nlet _mod = null\nlet _promise = null\n\nmodule.exports.init = function() {\n if (!_promise) _promise = import('./index.mjs').then(function(m) {\n _mod = m\n${exports.map(e => ` module.exports.${e} = m.${e}`).join('\\n')}\n return module.exports\n })\n return _promise\n}\n\n// Eagerly start loading so subsequent sync calls work after first await\n_promise = import('./index.mjs').then(function(m) {\n _mod = m\n${exports.map(e => ` module.exports.${e} = m.${e}`).join('\\n')}\n return module.exports\n}).catch(function() { _promise = null; /* retry on next init() call */ })\n`\n}\n\nfunction buildRelationMetaMap(\n models: ModelDefinition[],\n): Map<string, Record<string, { target: string | string[]; multi: boolean }>> {\n const result = new Map<string, Record<string, { target: string | string[]; multi: boolean }>>()\n\n for (const model of models) {\n if (!model.fields) continue\n const meta: Record<string, { target: string | string[]; multi: boolean }> = {}\n let hasRelations = false\n\n for (const [fieldName, field] of Object.entries(model.fields)) {\n if (field.type === 'relation' && field.model) {\n meta[fieldName] = { target: field.model, multi: false }\n hasRelations = true\n } else if (field.type === 'relations' && field.model) {\n meta[fieldName] = { target: field.model, multi: true }\n hasRelations = true\n }\n }\n\n if (hasRelations) {\n result.set(model.id, meta)\n }\n }\n\n return result\n}\n\nfunction fileNameToVar(fileName: string): string {\n // blog-post.en.mjs → _blogPostEn\n const base = fileName.replace('.mjs', '')\n const parts = base.split(/[-.]/)\n return '_' + parts.map((p, i) => i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)).join('')\n}\n\nfunction parseDataFileName(fileName: string): { modelId: string; locale: string | null } {\n const base = fileName.replace('.mjs', '')\n\n // Document files use \"modelId--slug.locale\" or \"modelId--slug\" format\n // Extract modelId from before \"--\", ignore slug part\n let nameForLocale = base\n if (base.includes('--')) {\n nameForLocale = base // locale is still the last dot-segment\n }\n\n // Extract locale from last dot-segment (e.g., \"blog-post.en\" → locale \"en\")\n const parts = nameForLocale.split('.')\n let locale: string | null = null\n let nameWithoutLocale = nameForLocale\n if (parts.length >= 2) {\n const possibleLocale = parts[parts.length - 1]!\n if (possibleLocale.length === 2 || possibleLocale.includes('-')) {\n locale = possibleLocale\n nameWithoutLocale = parts.slice(0, -1).join('.')\n }\n }\n\n // Extract modelId: if \"--\" present, it's the part before \"--\"\n const modelId = nameWithoutLocale.includes('--')\n ? nameWithoutLocale.split('--')[0]!\n : nameWithoutLocale\n\n return { modelId, locale }\n}\n\n// Inlined runtime code — zero dependencies\nconst RUNTIME_CODE = `\n// ─── Runtime Helpers ───\n\nfunction _applyWhere(item, clause) {\n const val = item[clause.field];\n switch (clause.op) {\n case 'eq': return Array.isArray(val) ? val.includes(clause.value) : val === clause.value;\n case 'ne': return Array.isArray(val) ? !val.includes(clause.value) : val !== clause.value;\n case 'gt': return val > clause.value;\n case 'gte': return val >= clause.value;\n case 'lt': return val < clause.value;\n case 'lte': return val <= clause.value;\n case 'in': return Array.isArray(clause.value) && clause.value.includes(val);\n case 'contains': {\n if (typeof val === 'string') return val.includes(clause.value);\n if (Array.isArray(val)) return val.includes(clause.value);\n return false;\n }\n default: return true;\n }\n}\n\n// ─── Runtime Classes ───\n\nclass QueryBuilder {\n constructor(data, relationMeta, resolver, defaultLocale) { this._data = data; this._filters = []; this._sortField = null; this._sortOrder = 'asc'; this._limit = null; this._offset = 0; this._locale = null; this._includes = []; this._relationMeta = relationMeta || {}; this._resolver = resolver || null; this._defaultLocale = defaultLocale || null; }\n locale(lang) { this._locale = lang; return this; }\n where(field, opOrValue, value) { if (value !== undefined) { this._filters.push({ field, op: opOrValue, value }); } else { this._filters.push({ field, op: 'eq', value: opOrValue }); } return this; }\n sort(field, order = 'asc') { this._sortField = field; this._sortOrder = order; return this; }\n limit(n) { this._limit = n; return this; }\n offset(n) { this._offset = n; return this; }\n include(...fields) { this._includes.push(...fields); return this; }\n count() { return this.all().length; }\n all() {\n let items; if (this._locale) { items = [...(this._data.get(this._locale) ?? [])]; } else if (this._defaultLocale && this._data.has(this._defaultLocale)) { items = [...this._data.get(this._defaultLocale)]; } else { items = [...(this._data.get(this._data.keys().next().value) ?? [])]; }\n for (const clause of this._filters) items = items.filter(item => _applyWhere(item, clause));\n if (this._sortField) { const sf = this._sortField; const d = this._sortOrder === 'asc' ? 1 : -1; items.sort((a, b) => { const va = a[sf], vb = b[sf]; if (va == null && vb == null) return 0; if (va == null) return d; if (vb == null) return -d; return va < vb ? -d : va > vb ? d : 0; }); }\n if (this._offset > 0 || this._limit !== null) { const end = this._limit !== null ? this._offset + this._limit : undefined; items = items.slice(this._offset, end); }\n if (this._includes.length > 0 && this._resolver) { items = items.map(item => this._resolveIncludes(item)); }\n return items;\n }\n first() { return this.all()[0]; }\n _resolveIncludes(item) {\n const resolved = { ...item };\n const _loc = this._locale ?? this._defaultLocale;\n for (const field of this._includes) {\n const meta = this._relationMeta[field]; if (!meta) continue;\n const targets = Array.isArray(meta.target) ? meta.target : [meta.target];\n if (meta.multi) { const ids = item[field]; if (Array.isArray(ids)) { resolved[field] = ids.map(id => { if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) return r; } return id; } if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) return r; } return id; }); } }\n else { const id = item[field]; if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) { resolved[field] = r; break; } } } else if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) resolved[field] = r; } }\n }\n return resolved;\n }\n}\n\nclass SingletonAccessor {\n constructor(data, defaultLocale, relationMeta, resolveEntry) { this._data = data; this._locale = null; this._defaultLocale = defaultLocale || null; this._includes = []; this._relationMeta = relationMeta || {}; this._resolver = resolveEntry; }\n locale(lang) { this._locale = lang; return this; }\n include(...fields) { this._includes.push(...fields); return this; }\n get() {\n const locale = this._locale || this._defaultLocale;\n let d = locale ? this._data.get(locale) : undefined;\n if (!d) { const key = this._data.keys().next().value; d = key !== undefined ? this._data.get(key) : undefined; }\n if (!d) throw new Error('No data available');\n if (this._includes.length > 0 && this._resolver) return this._resolveIncludes(d, locale || 'en');\n return d;\n }\n _resolveIncludes(item, locale) {\n const resolved = { ...item };\n for (const field of this._includes) {\n const meta = this._relationMeta[field]; if (!meta) continue;\n const targets = Array.isArray(meta.target) ? meta.target : [meta.target];\n if (meta.multi) { const ids = item[field]; if (Array.isArray(ids)) { resolved[field] = ids.map(id => { if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, locale); if (r) return r; } return id; } if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, locale); if (r) return r; } return id; }); } }\n else { const id = item[field]; if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, locale); if (r) { resolved[field] = r; break; } } } else if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, locale); if (r) resolved[field] = r; } }\n }\n return resolved;\n }\n}\n\nclass DictionaryAccessor {\n constructor(data, defaultLocale) { this._data = data; this._locale = null; this._defaultLocale = defaultLocale || null; }\n locale(lang) { this._locale = lang; return this; }\n get(key, params) {\n let dict; if (this._locale) { dict = this._data.get(this._locale) ?? {}; } else if (this._defaultLocale && this._data.has(this._defaultLocale)) { dict = this._data.get(this._defaultLocale); } else { const loc = this._data.keys().next().value; dict = this._data.get(loc) ?? {}; }\n if (key === undefined) return dict;\n const val = dict[key];\n if (val === undefined) return undefined;\n if (params) return val.replace(/\\\\{(\\\\w+)\\\\}/g, (m, k) => { const v = params[k]; return v !== undefined ? String(v) : m; });\n return val;\n }\n}\n\nclass DocumentQuery {\n constructor(data, relationMeta, resolver, defaultLocale) { this._data = data; this._filters = []; this._sortField = null; this._sortOrder = 'asc'; this._locale = null; this._includes = []; this._relationMeta = relationMeta || {}; this._resolver = resolver || null; this._defaultLocale = defaultLocale || null; }\n locale(lang) { this._locale = lang; return this; }\n where(field, opOrValue, value) { if (value !== undefined) { this._filters.push({ field, op: opOrValue, value }); } else { this._filters.push({ field, op: 'eq', value: opOrValue }); } return this; }\n sort(field, order = 'asc') { this._sortField = field; this._sortOrder = order; return this; }\n include(...fields) { this._includes.push(...fields); return this; }\n bySlug(slug) {\n const items = this._resolveData(); const item = items.find(x => x.slug === slug);\n if (item && this._includes.length > 0 && this._resolver) return this._resolveIncludes(item);\n return item;\n }\n count() { return this.all().length; }\n first() { return this.all()[0]; }\n all() { let items = this._resolveData(); for (const clause of this._filters) items = items.filter(item => _applyWhere(item, clause)); if (this._sortField) { const sf = this._sortField; const d = this._sortOrder === 'asc' ? 1 : -1; items.sort((a, b) => { const va = a[sf], vb = b[sf]; if (va == null && vb == null) return 0; if (va == null) return d; if (vb == null) return -d; return va < vb ? -d : va > vb ? d : 0; }); } if (this._includes.length > 0 && this._resolver) { items = items.map(item => this._resolveIncludes(item)); } return items; }\n _resolveData() { let key; if (this._locale) { key = this._locale; } else if (this._defaultLocale && this._data.has(this._defaultLocale)) { key = this._defaultLocale; } else { key = this._data.keys().next().value; } return [...(this._data.get(key) ?? [])]; }\n _resolveIncludes(item) {\n const resolved = { ...item };\n const _loc = this._locale ?? this._defaultLocale;\n for (const field of this._includes) {\n const meta = this._relationMeta[field]; if (!meta) continue;\n const targets = Array.isArray(meta.target) ? meta.target : [meta.target];\n if (meta.multi) { const ids = item[field]; if (Array.isArray(ids)) { resolved[field] = ids.map(id => { if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) return r; } return id; } if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) return r; } return id; }); } }\n else { const id = item[field]; if (typeof id === 'string') { for (const t of targets) { const r = this._resolver(t, id, _loc); if (r) { resolved[field] = r; break; } } } else if (typeof id === 'object' && id !== null && 'model' in id && 'ref' in id) { const r = this._resolver(id.model, id.ref, _loc); if (r) resolved[field] = r; } }\n }\n return resolved;\n }\n}\n`.trim()\n","import { join } from 'node:path'\nimport { readJson, writeText } from './utils.js'\n\nconst IMPORTS_CONFIG = {\n '#contentrain': {\n types: './.contentrain/client/index.d.ts',\n import: './.contentrain/client/index.mjs',\n require: './.contentrain/client/index.cjs',\n default: './.contentrain/client/index.mjs',\n },\n '#contentrain/*': {\n types: './.contentrain/client/*.d.ts',\n import: './.contentrain/client/*.mjs',\n require: './.contentrain/client/*.cjs',\n default: './.contentrain/client/*.mjs',\n },\n}\n\nexport async function injectImports(projectRoot: string): Promise<boolean> {\n const pkgPath = join(projectRoot, 'package.json')\n const pkg = await readJson<Record<string, unknown>>(pkgPath)\n if (!pkg) return false\n\n const existing = (pkg['imports'] as Record<string, unknown>) ?? {}\n const updated = { ...existing, ...IMPORTS_CONFIG }\n\n // Check if already matches\n if (JSON.stringify(existing['#contentrain']) === JSON.stringify(IMPORTS_CONFIG['#contentrain']) &&\n JSON.stringify(existing['#contentrain/*']) === JSON.stringify(IMPORTS_CONFIG['#contentrain/*'])) {\n return false // No change needed\n }\n\n pkg['imports'] = updated\n await writeText(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n return true\n}\n","import { publicationContext, type PublicationOptions } from './publication.js'\nimport { join } from 'node:path'\nimport { rm } from 'node:fs/promises'\nimport { readProjectManifest } from './config-reader.js'\nimport { emitTypes } from './type-emitter.js'\nimport { emitDataModules } from './data-emitter.js'\nimport { emitRuntimeModule, emitCjsWrapper } from './runtime-emitter.js'\nimport { injectImports } from './package-json.js'\nimport { readDir, writeText } from './utils.js'\n\nexport interface GenerateOptions extends PublicationOptions {\n projectRoot: string\n /**\n * Public media delivery base for resolving relative `media/...` references\n * (in image/video/file fields, and `](media/...)` links in markdown) to\n * absolute URLs. When set (or when `config.cdn.url` is present), the\n * emitted client gains `media()`/`mediaBody()` resolvers. The explicit\n * option wins over the config value. Omit for the pure local-file model —\n * media stays a relative path until a consumer resolves it itself, e.g.\n * via `resolveMediaUrl()`/`resolveMediaRefsInBody()` with a base sourced\n * from its own env or runtime config (a Nuxt app's `useRuntimeConfig()`).\n */\n mediaBaseUrl?: string\n /** @deprecated Alias for `mediaBaseUrl`, kept for existing callers. */\n cdnBaseUrl?: string\n}\n\nexport interface GenerateResult {\n generatedFiles: string[]\n typesCount: number\n dataModulesCount: number\n packageJsonUpdated: boolean\n}\n\nexport async function generate(options: GenerateOptions): Promise<GenerateResult> {\n const { projectRoot } = options\n const clientDir = join(projectRoot, '.contentrain', 'client')\n const dataDir = join(clientDir, 'data')\n\n // 1. Read project manifest\n const manifest = await readProjectManifest(projectRoot)\n\n // Media delivery base: explicit option wins over config.cdn.url. When set,\n // the emitted client gains `media()`/`mediaBody()` resolvers.\n const mediaBaseUrl = options.mediaBaseUrl ?? options.cdnBaseUrl ?? manifest.config.cdn?.url\n const hasMedia = Boolean(mediaBaseUrl)\n\n // 2. Generate data modules (async — reads content files)\n const dataModules = await emitDataModules(manifest.models, manifest.contentFiles, publicationContext(projectRoot, manifest.config.locales.default, options))\n\n // 3. Generate all output content (sync — pure string transforms)\n const typesContent = emitTypes(manifest.models, hasMedia)\n const runtimeContent = emitRuntimeModule(manifest.models, dataModules, manifest.config.locales.default, mediaBaseUrl)\n const cjsContent = emitCjsWrapper(manifest.models, hasMedia)\n\n // 4. Clean stale data modules\n const newFileNames = new Set(dataModules.map(dm => dm.fileName))\n try {\n const existing = await readDir(dataDir)\n await Promise.all(\n existing\n .filter(f => !newFileNames.has(f))\n .map(f => rm(join(dataDir, f), { force: true })),\n )\n } catch { /* dataDir may not exist yet */ }\n\n // 5. Write all files in parallel\n const dataFileNames = dataModules.map(dm => `data/${dm.fileName}`)\n await Promise.all([\n writeText(join(clientDir, 'index.d.ts'), typesContent),\n writeText(join(clientDir, 'index.mjs'), runtimeContent),\n writeText(join(clientDir, 'index.cjs'), cjsContent),\n ...dataModules.map(dm => writeText(join(dataDir, dm.fileName), dm.content)),\n ])\n\n // 6. Inject #imports into package.json\n const packageJsonUpdated = await injectImports(projectRoot)\n\n return {\n generatedFiles: ['index.d.ts', ...dataFileNames, 'index.mjs', 'index.cjs'],\n typesCount: manifest.models.length,\n dataModulesCount: dataModules.length,\n packageJsonUpdated,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as generate } from "../generate-
|
|
1
|
+
import { t as generate } from "../generate-1RkU4u-9.mjs";
|
|
2
2
|
export { generate };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publication-C90PRY0Y.d.mts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"publication-C90PRY0Y.d.mts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";UAMiB,kBAAA;;EAEf,aAAA;EAAA;EAEA,EAAA;EAQA;;;;;;;EAAA,aAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publication-CH4OXXzW.d.cts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"publication-CH4OXXzW.d.cts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";UAMiB,kBAAA;;EAEf,aAAA;EAAA;EAEA,EAAA;EAQA;;;;;;;EAAA,aAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/query",
|
|
3
|
-
"version": "7.6.
|
|
3
|
+
"version": "7.6.41",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Optional type-safe query SDK for Contentrain — generated TypeScript client for platform-independent JSON content",
|
|
6
6
|
"type": "module",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"skills"
|
|
90
90
|
],
|
|
91
91
|
"dependencies": {
|
|
92
|
-
"@contentrain/types": "1.
|
|
92
|
+
"@contentrain/types": "1.61.0"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@types/node": "^22.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter-Bf6Y8vAj.mjs","names":[],"sources":["../src/generator/publication.ts","../src/generator/utils.ts","../src/generator/config-reader.ts","../src/shared/frontmatter.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { ModelDefinition } from '@contentrain/types'\nimport type { ContentFileRef } from './config-reader.js'\n\nexport interface PublicationOptions {\n /** Opt in for public builds; the default keeps the existing editorial client. */\n publishedOnly?: boolean\n /** ISO timestamp for a reproducible public build. Implies publishedOnly. */\n at?: string\n /**\n * Hold back an entry that carries no workflow status — no meta record, or a\n * record without `status`. Without it such an entry stays visible (legacy\n * content predates meta). Set it when the producer writes a status for every\n * entry, as the WordPress import does: a missing status then means a broken\n * import, and a public build must not guess. Implies publishedOnly.\n */\n requireStatus?: boolean\n}\n\nexport interface PublicationContext {\n projectRoot: string\n defaultLocale: string\n at: number\n requireStatus: boolean\n}\n\nexport function publicationContext(projectRoot: string, defaultLocale: string, options: PublicationOptions): PublicationContext | undefined {\n if (!options.publishedOnly && !options.requireStatus && options.at === undefined) return undefined\n if (options.at !== undefined && !/^\\d{4}-\\d{2}-\\d{2}T.*(?:Z|[+-]\\d{2}:\\d{2})$/i.test(options.at))\n throw new Error('Invalid publication --at timestamp: include an ISO date, time and timezone')\n const at = options.at === undefined ? Date.now() : Date.parse(options.at)\n if (!Number.isFinite(at)) throw new Error('Invalid publication --at timestamp')\n return { projectRoot, defaultLocale, at, requireStatus: options.requireStatus === true }\n}\n\n/** Canonical meta is independent of custom content paths and locale strategies. */\nexport async function publicationMeta(ref: ContentFileRef, model: ModelDefinition, context: PublicationContext): Promise<Record<string, unknown> | undefined> {\n const locale = model.i18n ? ref.locale ?? context.defaultLocale : context.defaultLocale\n const parts = [context.projectRoot, '.contentrain', 'meta', model.id]\n if (model.kind === 'document') parts.push(ref.slug ?? model.id)\n const path = join(...parts, `${locale}.json`)\n let text: string\n try { text = await readFile(path, 'utf8') }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined\n throw error\n }\n const meta: unknown = JSON.parse(text)\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) throw new Error(`Invalid publication metadata: ${path}`)\n return meta as Record<string, unknown>\n}\n\n/**\n * Legacy content without meta remains visible, matching Studio's CDN contract —\n * unless `requireStatus`, where an entry without a status (missing or empty) is held back.\n */\nexport function isPublishedAt(meta: unknown, at: number, requireStatus = false): boolean {\n if (meta === undefined) return !requireStatus\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) return false\n const value = meta as Record<string, unknown>\n if (!value.status && requireStatus) return false\n if (value.status && value.status !== 'published') return false\n for (const key of ['publish_at', 'expire_at'] as const) {\n if (value[key] === undefined || value[key] === null) continue\n const time = typeof value[key] === 'string' ? Date.parse(value[key]) : NaN\n if (!Number.isFinite(time)) return false\n if (key === 'publish_at' ? time > at : time <= at) return false\n }\n return true\n}\n","import { readFile, readdir, writeFile, mkdir } from 'node:fs/promises'\nimport { join, dirname } from 'node:path'\n\nexport async function readJson<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await readFile(filePath, 'utf-8')\n return JSON.parse(raw) as T\n } catch {\n return null\n }\n}\n\nexport async function readDir(dirPath: string): Promise<string[]> {\n try {\n return await readdir(dirPath)\n } catch {\n return []\n }\n}\n\nexport async function readText(filePath: string): Promise<string | null> {\n try {\n return await readFile(filePath, 'utf-8')\n } catch {\n return null\n }\n}\n\nexport async function writeText(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true })\n await writeFile(filePath, content, 'utf-8')\n}\n\nexport function contentrainDir(projectRoot: string): string {\n return join(projectRoot, '.contentrain')\n}\n","import type { ContentrainConfig, ModelDefinition, LocaleStrategy } from '@contentrain/types'\nimport { join } from 'node:path'\nimport { readJson, readDir, readText, contentrainDir } from './utils.js'\n\nexport interface ContentFileRef {\n modelId: string\n locale: string | null\n filePath: string\n kind: ModelDefinition['kind']\n /** Extracted slug for document kind (from filename, not frontmatter) */\n slug?: string\n}\n\nexport interface ProjectManifest {\n config: ContentrainConfig\n models: ModelDefinition[]\n contentFiles: ContentFileRef[]\n}\n\nexport async function readProjectManifest(projectRoot: string): Promise<ProjectManifest> {\n const crDir = contentrainDir(projectRoot)\n\n // 1. Read config\n const rawConfig = await readJson<Partial<ContentrainConfig>>(join(crDir, 'config.json'))\n const config: ContentrainConfig = {\n version: rawConfig?.version ?? 1,\n stack: (rawConfig?.stack ?? 'other') as ContentrainConfig['stack'],\n workflow: (rawConfig?.workflow ?? 'auto-merge') as ContentrainConfig['workflow'],\n locales: {\n default: rawConfig?.locales?.default ?? 'en',\n supported: rawConfig?.locales?.supported ?? [rawConfig?.locales?.default ?? 'en'],\n },\n domains: rawConfig?.domains ?? [],\n repository: rawConfig?.repository,\n assets_path: rawConfig?.assets_path,\n cdn: rawConfig?.cdn,\n branchRetention: rawConfig?.branchRetention,\n }\n\n // 2. Read all models (parallel)\n const modelsDir = join(crDir, 'models')\n const modelFiles = (await readDir(modelsDir)).filter(f => f.endsWith('.json'))\n const modelResults = await Promise.all(\n modelFiles.map(file => readJson<ModelDefinition>(join(modelsDir, file))),\n )\n const models = modelResults\n .filter((m): m is ModelDefinition => m != null && Boolean(m.id))\n .toSorted((a, b) => a.id.localeCompare(b.id, 'en'))\n\n // 3. Map content files (parallel per model)\n const allRefs = await Promise.all(\n models.map(model => mapContentFiles(projectRoot, model, config)),\n )\n const contentFiles = allRefs.flat()\n\n return { config, models, contentFiles }\n}\n\nfunction getContentDir(projectRoot: string, model: ModelDefinition): string {\n if (model.content_path) return join(projectRoot, model.content_path)\n return join(contentrainDir(projectRoot), 'content', model.domain, model.id)\n}\n\nfunction getLocaleStrategy(model: ModelDefinition): LocaleStrategy {\n return model.locale_strategy ?? 'file'\n}\n\nfunction jsonFilePath(dir: string, model: ModelDefinition, locale: string): string {\n const strategy = getLocaleStrategy(model)\n switch (strategy) {\n case 'suffix': return join(dir, `${model.id}.${locale}.json`)\n case 'directory': return join(dir, locale, `${model.id}.json`)\n case 'none': return join(dir, `${model.id}.json`)\n case 'file':\n default: return join(dir, `${locale}.json`)\n }\n}\n\nasync function mapContentFiles(\n projectRoot: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n): Promise<ContentFileRef[]> {\n const dir = getContentDir(projectRoot, model)\n\n if (model.kind === 'document') {\n return mapDocumentFiles(dir, model, config)\n }\n\n // JSON-based models (singleton, collection, dictionary)\n if (model.i18n) {\n const results = await Promise.all(\n config.locales.supported.map(async (locale) => {\n const filePath = jsonFilePath(dir, model, locale)\n const content = await readJson(filePath)\n if (content === null) return null\n return { modelId: model.id, locale, filePath, kind: model.kind } as ContentFileRef\n }),\n )\n return results.filter(Boolean) as ContentFileRef[]\n }\n\n // Non-i18n: always data.json\n const filePath = join(dir, 'data.json')\n const content = await readJson(filePath)\n if (content === null) return []\n return [{ modelId: model.id, locale: null, filePath, kind: model.kind }]\n}\n\nasync function mapDocumentFiles(\n dir: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n): Promise<ContentFileRef[]> {\n const strategy = getLocaleStrategy(model)\n\n if (!model.i18n) {\n // Non-i18n documents: {dir}/{slug}.md\n const files = (await readDir(dir)).filter(f => f.endsWith('.md'))\n return files.map(f => ({\n modelId: model.id,\n locale: null,\n filePath: join(dir, f),\n kind: 'document' as const,\n slug: f.replace('.md', ''),\n }))\n }\n\n // i18n documents — resolve per locale in parallel\n const allLocaleRefs = await Promise.all(\n config.locales.supported.map(locale => mapDocumentLocale(dir, model, locale, strategy)),\n )\n return allLocaleRefs.flat()\n}\n\nasync function mapDocumentLocale(\n dir: string,\n model: ModelDefinition,\n locale: string,\n strategy: LocaleStrategy,\n): Promise<ContentFileRef[]> {\n if (strategy === 'file') {\n // Each slug is a directory: {dir}/{slug}/{locale}.md\n const entries = await readDir(dir)\n const results = await Promise.all(\n entries.map(async (entry) => {\n const filePath = join(dir, entry, `${locale}.md`)\n const content = await readText(filePath)\n if (content === null) return null\n return { modelId: model.id, locale, filePath, kind: 'document', slug: entry } as ContentFileRef\n }),\n )\n return results.filter(Boolean) as ContentFileRef[]\n }\n\n if (strategy === 'directory') {\n // {dir}/{locale}/{slug}.md\n const localeDir = join(dir, locale)\n const files = (await readDir(localeDir)).filter(f => f.endsWith('.md'))\n return files.map(f => ({\n modelId: model.id,\n locale,\n filePath: join(localeDir, f),\n kind: 'document' as const,\n slug: f.replace('.md', ''),\n }))\n }\n\n if (strategy === 'suffix') {\n // {dir}/{slug}.{locale}.md\n const suffix = `.${locale}.md`\n const files = (await readDir(dir)).filter(f => f.endsWith(suffix))\n return files.map(f => ({\n modelId: model.id,\n locale,\n filePath: join(dir, f),\n kind: 'document' as const,\n slug: f.slice(0, -suffix.length),\n }))\n }\n\n return []\n}\n","// Frontmatter parsing shared by the client generator and the Astro loader.\n//\n// Both read the same `.contentrain` documents, so the parser lives here rather\n// than being replicated: a divergence would mean the generated client and the\n// Astro collection disagree about the same file's fields.\n//\n// The same argument reaches one level further out. The content engine reads\n// these documents too, through `@contentrain/types`, and for a while this file\n// carried its own copy of the scalar rules — so the two disagreed about what a\n// quoted string with escapes in it says. The *structure* below (nesting,\n// indentation, the model-aware string coercion) is this package's; the scalar\n// grammar is imported, because how a value is spelled on disk is a contract\n// rather than an implementation detail.\n\nimport type { ModelDefinition } from '@contentrain/types'\nimport {\n parseFrontmatterScalar,\n parseFrontmatterScalarString,\n splitFrontmatterList,\n} from '@contentrain/types'\n\n// Field types that map to `string` in the generated types — their frontmatter\n// values must NOT be numerically coerced (e.g. a string SKU \"007\").\nconst STRING_LIKE_TYPES = new Set([\n 'string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext', 'date', 'datetime', 'image', 'video', 'file',\n 'select', 'relation',\n])\n\nexport function stringLikeFieldKeys(model: ModelDefinition): Set<string> {\n const keys = new Set<string>()\n if (model.fields) {\n for (const [name, field] of Object.entries(model.fields)) {\n if (STRING_LIKE_TYPES.has(field.type)) keys.add(name)\n }\n }\n return keys\n}\n\n// Minimal frontmatter parser (replicated from MCP pattern)\nexport function parseFrontmatter(text: string, stringKeys: Set<string> = new Set()): { frontmatter: Record<string, unknown>; body: string } {\n const normalized = text.replace(/\\r\\n/g, '\\n')\n const match = normalized.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/)\n if (!match) return { frontmatter: {}, body: normalized }\n\n const fmStr = match[1]!\n const body = match[2]!.trim()\n const frontmatter: Record<string, unknown> = {}\n\n // Stack-based parser that handles nested objects and arrays\n const lines = fmStr.split('\\n')\n const stack: Array<{ obj: Record<string, unknown>; indent: number }> = [{ obj: frontmatter, indent: -1 }]\n\n for (const [lineIndex, line] of lines.entries()) {\n // Skip empty lines\n if (line.trim() === '') continue\n\n // Array item\n const arrayMatch = line.match(/^(\\s*)-\\s+(.*)$/)\n if (arrayMatch) {\n const arrIndent = arrayMatch[1]!.length\n const value = arrayMatch[2]!.trim()\n // Find the parent that owns this array\n while (stack.length > 1 && stack[stack.length - 1]!.indent >= arrIndent) {\n stack.pop()\n }\n const parent = stack[stack.length - 1]!.obj\n const lastKey = Object.keys(parent).pop()\n if (lastKey && Array.isArray(parent[lastKey])) {\n (parent[lastKey] as unknown[]).push(parseValue(value))\n }\n continue\n }\n\n // Key-value pair\n const kvMatch = line.match(/^(\\s*)([\\w][\\w.-]*)\\s*:\\s*(.*)$/)\n if (!kvMatch) continue\n\n const kvIndent = kvMatch[1]!.length\n const key = kvMatch[2]!\n const rawValue = kvMatch[3]!.trim()\n\n // Pop stack to find correct parent based on indentation\n while (stack.length > 1 && stack[stack.length - 1]!.indent >= kvIndent) {\n stack.pop()\n }\n const current = stack[stack.length - 1]!.obj\n\n if (rawValue === '') {\n // A bare `key:` is ambiguous: empty array, empty object, or null. It is\n // resolved by what follows — a list item opens an array, a more-indented\n // key opens an object, and anything else (including end of frontmatter)\n // is an empty array, which is what the canonical reader in\n // `@contentrain/types` returns. Documents written today say `key: []`\n // outright; this path is for the ones written before that.\n //\n // The peek used `lines.indexOf(line)`, which finds the FIRST line with\n // this text rather than this one — two identically spelled lines in one\n // document made the second read the first one's successor.\n let nextLine = ''\n for (let i = lineIndex + 1; i < lines.length; i += 1) {\n if (lines[i]!.trim() !== '') {\n nextLine = lines[i]!\n break\n }\n }\n const nextIndent = nextLine.length - nextLine.trimStart().length\n if (nextLine.trim().startsWith('-')) {\n current[key] = []\n } else if (nextLine !== '' && nextIndent > kvIndent) {\n const nested: Record<string, unknown> = {}\n current[key] = nested\n stack.push({ obj: nested, indent: kvIndent })\n } else {\n current[key] = []\n }\n continue\n }\n\n if (rawValue.startsWith('[') && rawValue.endsWith(']')) {\n const inner = rawValue.slice(1, -1).trim()\n current[key] = inner === ''\n ? []\n : splitFrontmatterList(inner).map(item => parseFrontmatterScalarString(item.trim()))\n continue\n }\n\n // Only top-level keys are matched against the model's declared field types.\n const forceString = current === frontmatter && stringKeys.has(key)\n current[key] = parseValue(rawValue, forceString)\n }\n\n return { frontmatter, body }\n}\n\nfunction parseValue(raw: string, forceString = false): unknown {\n return forceString ? parseFrontmatterScalarString(raw) : parseFrontmatterScalar(raw)\n}\n"],"mappings":";;;;AA2BA,SAAgB,mBAAmB,aAAqB,eAAuB,SAA6D;AAC1I,KAAI,CAAC,QAAQ,iBAAiB,CAAC,QAAQ,iBAAiB,QAAQ,OAAO,KAAA,EAAW,QAAO,KAAA;AACzF,KAAI,QAAQ,OAAO,KAAA,KAAa,CAAC,+CAA+C,KAAK,QAAQ,GAAG,CAC9F,OAAM,IAAI,MAAM,6EAA6E;CAC/F,MAAM,KAAK,QAAQ,OAAO,KAAA,IAAY,KAAK,KAAK,GAAG,KAAK,MAAM,QAAQ,GAAG;AACzE,KAAI,CAAC,OAAO,SAAS,GAAG,CAAE,OAAM,IAAI,MAAM,qCAAqC;AAC/E,QAAO;EAAE;EAAa;EAAe;EAAI,eAAe,QAAQ,kBAAkB;EAAM;;;AAI1F,eAAsB,gBAAgB,KAAqB,OAAwB,SAA2E;CAC5J,MAAM,SAAS,MAAM,OAAO,IAAI,UAAU,QAAQ,gBAAgB,QAAQ;CAC1E,MAAM,QAAQ;EAAC,QAAQ;EAAa;EAAgB;EAAQ,MAAM;EAAG;AACrE,KAAI,MAAM,SAAS,WAAY,OAAM,KAAK,IAAI,QAAQ,MAAM,GAAG;CAC/D,MAAM,OAAO,KAAK,GAAG,OAAO,GAAG,OAAO,OAAO;CAC7C,IAAI;AACJ,KAAI;AAAE,SAAO,MAAM,SAAS,MAAM,OAAO;UAClC,OAAO;AACZ,MAAK,MAAgC,SAAS,SAAU,QAAO,KAAA;AAC/D,QAAM;;CAER,MAAM,OAAgB,KAAK,MAAM,KAAK;AACtC,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAAE,OAAM,IAAI,MAAM,iCAAiC,OAAO;AACtH,QAAO;;;;;;AAOT,SAAgB,cAAc,MAAe,IAAY,gBAAgB,OAAgB;AACvF,KAAI,SAAS,KAAA,EAAW,QAAO,CAAC;AAChC,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAAE,QAAO;CACrE,MAAM,QAAQ;AACd,KAAI,CAAC,MAAM,UAAU,cAAe,QAAO;AAC3C,KAAI,MAAM,UAAU,MAAM,WAAW,YAAa,QAAO;AACzD,MAAK,MAAM,OAAO,CAAC,cAAc,YAAY,EAAW;AACtD,MAAI,MAAM,SAAS,KAAA,KAAa,MAAM,SAAS,KAAM;EACrD,MAAM,OAAO,OAAO,MAAM,SAAS,WAAW,KAAK,MAAM,MAAM,KAAK,GAAG;AACvE,MAAI,CAAC,OAAO,SAAS,KAAK,CAAE,QAAO;AACnC,MAAI,QAAQ,eAAe,OAAO,KAAK,QAAQ,GAAI,QAAO;;AAE5D,QAAO;;;;AClET,eAAsB,SAAY,UAAqC;AACrE,KAAI;EACF,MAAM,MAAM,MAAM,SAAS,UAAU,QAAQ;AAC7C,SAAO,KAAK,MAAM,IAAI;SAChB;AACN,SAAO;;;AAIX,eAAsB,QAAQ,SAAoC;AAChE,KAAI;AACF,SAAO,MAAM,QAAQ,QAAQ;SACvB;AACN,SAAO,EAAE;;;AAIb,eAAsB,SAAS,UAA0C;AACvE,KAAI;AACF,SAAO,MAAM,SAAS,UAAU,QAAQ;SAClC;AACN,SAAO;;;AAIX,eAAsB,UAAU,UAAkB,SAAgC;AAChF,OAAM,MAAM,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,OAAM,UAAU,UAAU,SAAS,QAAQ;;AAG7C,SAAgB,eAAe,aAA6B;AAC1D,QAAO,KAAK,aAAa,eAAe;;;;ACf1C,eAAsB,oBAAoB,aAA+C;CACvF,MAAM,QAAQ,eAAe,YAAY;CAGzC,MAAM,YAAY,MAAM,SAAqC,KAAK,OAAO,cAAc,CAAC;CACxF,MAAM,SAA4B;EAChC,SAAS,WAAW,WAAW;EAC/B,OAAQ,WAAW,SAAS;EAC5B,UAAW,WAAW,YAAY;EAClC,SAAS;GACP,SAAS,WAAW,SAAS,WAAW;GACxC,WAAW,WAAW,SAAS,aAAa,CAAC,WAAW,SAAS,WAAW,KAAK;GAClF;EACD,SAAS,WAAW,WAAW,EAAE;EACjC,YAAY,WAAW;EACvB,aAAa,WAAW;EACxB,KAAK,WAAW;EAChB,iBAAiB,WAAW;EAC7B;CAGD,MAAM,YAAY,KAAK,OAAO,SAAS;CACvC,MAAM,cAAc,MAAM,QAAQ,UAAU,EAAE,QAAO,MAAK,EAAE,SAAS,QAAQ,CAAC;CAI9E,MAAM,UAHe,MAAM,QAAQ,IACjC,WAAW,KAAI,SAAQ,SAA0B,KAAK,WAAW,KAAK,CAAC,CAAC,CACzE,EAEE,QAAQ,MAA4B,KAAK,QAAQ,QAAQ,EAAE,GAAG,CAAC,CAC/D,UAAU,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,IAAI,KAAK,CAAC;AAQrD,QAAO;EAAE;EAAQ;EAAQ,eALT,MAAM,QAAQ,IAC5B,OAAO,KAAI,UAAS,gBAAgB,aAAa,OAAO,OAAO,CAAC,CACjE,EAC4B,MAAM;EAEI;;AAGzC,SAAS,cAAc,aAAqB,OAAgC;AAC1E,KAAI,MAAM,aAAc,QAAO,KAAK,aAAa,MAAM,aAAa;AACpE,QAAO,KAAK,eAAe,YAAY,EAAE,WAAW,MAAM,QAAQ,MAAM,GAAG;;AAG7E,SAAS,kBAAkB,OAAwC;AACjE,QAAO,MAAM,mBAAmB;;AAGlC,SAAS,aAAa,KAAa,OAAwB,QAAwB;AAEjF,SADiB,kBAAkB,MAAM,EACzC;EACE,KAAK,SAAU,QAAO,KAAK,KAAK,GAAG,MAAM,GAAG,GAAG,OAAO,OAAO;EAC7D,KAAK,YAAa,QAAO,KAAK,KAAK,QAAQ,GAAG,MAAM,GAAG,OAAO;EAC9D,KAAK,OAAQ,QAAO,KAAK,KAAK,GAAG,MAAM,GAAG,OAAO;EAEjD,QAAS,QAAO,KAAK,KAAK,GAAG,OAAO,OAAO;;;AAI/C,eAAe,gBACb,aACA,OACA,QAC2B;CAC3B,MAAM,MAAM,cAAc,aAAa,MAAM;AAE7C,KAAI,MAAM,SAAS,WACjB,QAAO,iBAAiB,KAAK,OAAO,OAAO;AAI7C,KAAI,MAAM,KASR,SARgB,MAAM,QAAQ,IAC5B,OAAO,QAAQ,UAAU,IAAI,OAAO,WAAW;EAC7C,MAAM,WAAW,aAAa,KAAK,OAAO,OAAO;AAEjD,MADgB,MAAM,SAAS,SAAS,KACxB,KAAM,QAAO;AAC7B,SAAO;GAAE,SAAS,MAAM;GAAI;GAAQ;GAAU,MAAM,MAAM;GAAM;GAChE,CACH,EACc,OAAO,QAAQ;CAIhC,MAAM,WAAW,KAAK,KAAK,YAAY;AAEvC,KADgB,MAAM,SAAS,SAAS,KACxB,KAAM,QAAO,EAAE;AAC/B,QAAO,CAAC;EAAE,SAAS,MAAM;EAAI,QAAQ;EAAM;EAAU,MAAM,MAAM;EAAM,CAAC;;AAG1E,eAAe,iBACb,KACA,OACA,QAC2B;CAC3B,MAAM,WAAW,kBAAkB,MAAM;AAEzC,KAAI,CAAC,MAAM,KAGT,SADe,MAAM,QAAQ,IAAI,EAAE,QAAO,MAAK,EAAE,SAAS,MAAM,CAAC,CACpD,KAAI,OAAM;EACrB,SAAS,MAAM;EACf,QAAQ;EACR,UAAU,KAAK,KAAK,EAAE;EACtB,MAAM;EACN,MAAM,EAAE,QAAQ,OAAO,GAAG;EAC3B,EAAE;AAOL,SAHsB,MAAM,QAAQ,IAClC,OAAO,QAAQ,UAAU,KAAI,WAAU,kBAAkB,KAAK,OAAO,QAAQ,SAAS,CAAC,CACxF,EACoB,MAAM;;AAG7B,eAAe,kBACb,KACA,OACA,QACA,UAC2B;AAC3B,KAAI,aAAa,QAAQ;EAEvB,MAAM,UAAU,MAAM,QAAQ,IAAI;AASlC,UARgB,MAAM,QAAQ,IAC5B,QAAQ,IAAI,OAAO,UAAU;GAC3B,MAAM,WAAW,KAAK,KAAK,OAAO,GAAG,OAAO,KAAK;AAEjD,OADgB,MAAM,SAAS,SAAS,KACxB,KAAM,QAAO;AAC7B,UAAO;IAAE,SAAS,MAAM;IAAI;IAAQ;IAAU,MAAM;IAAY,MAAM;IAAO;IAC7E,CACH,EACc,OAAO,QAAQ;;AAGhC,KAAI,aAAa,aAAa;EAE5B,MAAM,YAAY,KAAK,KAAK,OAAO;AAEnC,UADe,MAAM,QAAQ,UAAU,EAAE,QAAO,MAAK,EAAE,SAAS,MAAM,CAAC,CAC1D,KAAI,OAAM;GACrB,SAAS,MAAM;GACf;GACA,UAAU,KAAK,WAAW,EAAE;GAC5B,MAAM;GACN,MAAM,EAAE,QAAQ,OAAO,GAAG;GAC3B,EAAE;;AAGL,KAAI,aAAa,UAAU;EAEzB,MAAM,SAAS,IAAI,OAAO;AAE1B,UADe,MAAM,QAAQ,IAAI,EAAE,QAAO,MAAK,EAAE,SAAS,OAAO,CAAC,CACrD,KAAI,OAAM;GACrB,SAAS,MAAM;GACf;GACA,UAAU,KAAK,KAAK,EAAE;GACtB,MAAM;GACN,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,OAAO;GACjC,EAAE;;AAGL,QAAO,EAAE;;;;AC9JX,MAAM,oBAAoB,IAAI,IAAI;CAChC;CAAU;CAAQ;CAAS;CAAO;CAAQ;CAAS;CAAS;CAAQ;CACpE;CAAY;CAAY;CAAQ;CAAY;CAAS;CAAS;CAC9D;CAAU;CACX,CAAC;AAEF,SAAgB,oBAAoB,OAAqC;CACvE,MAAM,uBAAO,IAAI,KAAa;AAC9B,KAAI,MAAM;OACH,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,OAAO,CACtD,KAAI,kBAAkB,IAAI,MAAM,KAAK,CAAE,MAAK,IAAI,KAAK;;AAGzD,QAAO;;AAIT,SAAgB,iBAAiB,MAAc,6BAA0B,IAAI,KAAK,EAA0D;CAC1I,MAAM,aAAa,KAAK,QAAQ,SAAS,KAAK;CAC9C,MAAM,QAAQ,WAAW,MAAM,qCAAqC;AACpE,KAAI,CAAC,MAAO,QAAO;EAAE,aAAa,EAAE;EAAE,MAAM;EAAY;CAExD,MAAM,QAAQ,MAAM;CACpB,MAAM,OAAO,MAAM,GAAI,MAAM;CAC7B,MAAM,cAAuC,EAAE;CAG/C,MAAM,QAAQ,MAAM,MAAM,KAAK;CAC/B,MAAM,QAAiE,CAAC;EAAE,KAAK;EAAa,QAAQ;EAAI,CAAC;AAEzG,MAAK,MAAM,CAAC,WAAW,SAAS,MAAM,SAAS,EAAE;AAE/C,MAAI,KAAK,MAAM,KAAK,GAAI;EAGxB,MAAM,aAAa,KAAK,MAAM,kBAAkB;AAChD,MAAI,YAAY;GACd,MAAM,YAAY,WAAW,GAAI;GACjC,MAAM,QAAQ,WAAW,GAAI,MAAM;AAEnC,UAAO,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,GAAI,UAAU,UAC5D,OAAM,KAAK;GAEb,MAAM,SAAS,MAAM,MAAM,SAAS,GAAI;GACxC,MAAM,UAAU,OAAO,KAAK,OAAO,CAAC,KAAK;AACzC,OAAI,WAAW,MAAM,QAAQ,OAAO,SAAS,CAC1C,QAAO,SAAuB,KAAK,WAAW,MAAM,CAAC;AAExD;;EAIF,MAAM,UAAU,KAAK,MAAM,kCAAkC;AAC7D,MAAI,CAAC,QAAS;EAEd,MAAM,WAAW,QAAQ,GAAI;EAC7B,MAAM,MAAM,QAAQ;EACpB,MAAM,WAAW,QAAQ,GAAI,MAAM;AAGnC,SAAO,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,GAAI,UAAU,SAC5D,OAAM,KAAK;EAEb,MAAM,UAAU,MAAM,MAAM,SAAS,GAAI;AAEzC,MAAI,aAAa,IAAI;GAWnB,IAAI,WAAW;AACf,QAAK,IAAI,IAAI,YAAY,GAAG,IAAI,MAAM,QAAQ,KAAK,EACjD,KAAI,MAAM,GAAI,MAAM,KAAK,IAAI;AAC3B,eAAW,MAAM;AACjB;;GAGJ,MAAM,aAAa,SAAS,SAAS,SAAS,WAAW,CAAC;AAC1D,OAAI,SAAS,MAAM,CAAC,WAAW,IAAI,CACjC,SAAQ,OAAO,EAAE;YACR,aAAa,MAAM,aAAa,UAAU;IACnD,MAAM,SAAkC,EAAE;AAC1C,YAAQ,OAAO;AACf,UAAM,KAAK;KAAE,KAAK;KAAQ,QAAQ;KAAU,CAAC;SAE7C,SAAQ,OAAO,EAAE;AAEnB;;AAGF,MAAI,SAAS,WAAW,IAAI,IAAI,SAAS,SAAS,IAAI,EAAE;GACtD,MAAM,QAAQ,SAAS,MAAM,GAAG,GAAG,CAAC,MAAM;AAC1C,WAAQ,OAAO,UAAU,KACrB,EAAE,GACF,qBAAqB,MAAM,CAAC,KAAI,SAAQ,6BAA6B,KAAK,MAAM,CAAC,CAAC;AACtF;;AAKF,UAAQ,OAAO,WAAW,UADN,YAAY,eAAe,WAAW,IAAI,IAAI,CAClB;;AAGlD,QAAO;EAAE;EAAa;EAAM;;AAG9B,SAAS,WAAW,KAAa,cAAc,OAAgB;AAC7D,QAAO,cAAc,6BAA6B,IAAI,GAAG,uBAAuB,IAAI"}
|