@contentrain/query 7.5.1 → 7.6.1
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 +6 -0
- package/dist/astro/index.cjs +4 -4
- package/dist/astro/index.d.cts +18 -11
- package/dist/astro/index.d.cts.map +1 -1
- package/dist/astro/index.d.mts +18 -11
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +4 -4
- package/dist/astro/index.mjs.map +1 -1
- package/dist/cli.cjs +5 -3
- package/dist/cli.mjs +5 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/{frontmatter-D9yVqC5y.mjs → frontmatter-Bf6Y8vAj.mjs} +11 -6
- package/dist/frontmatter-Bf6Y8vAj.mjs.map +1 -0
- package/dist/{frontmatter-DSpE3so9.cjs → frontmatter-CvpQ8lJn.cjs} +10 -5
- package/dist/{generate-BOi6o7Z5.mjs → generate-BEfRkd7j.mjs} +5 -6
- package/dist/generate-BEfRkd7j.mjs.map +1 -0
- package/dist/{generate-D4NDHZ_K.cjs → generate-BwG58cTu.cjs} +4 -5
- package/dist/generator/generate.cjs +1 -1
- package/dist/generator/generate.d.cts +1 -1
- package/dist/generator/generate.d.mts +1 -1
- package/dist/generator/generate.mjs +1 -1
- package/dist/publication-C90PRY0Y.d.mts +18 -0
- package/dist/publication-C90PRY0Y.d.mts.map +1 -0
- package/dist/publication-CH4OXXzW.d.cts +18 -0
- package/dist/publication-CH4OXXzW.d.cts.map +1 -0
- package/package.json +2 -2
- package/dist/frontmatter-D9yVqC5y.mjs.map +0 -1
- package/dist/generate-BOi6o7Z5.mjs.map +0 -1
- package/dist/publication-CMrWLPi7.d.mts +0 -10
- package/dist/publication-CMrWLPi7.d.mts.map +0 -1
- package/dist/publication-DW0WaAuC.d.cts +0 -10
- package/dist/publication-DW0WaAuC.d.cts.map +0 -1
package/README.md
CHANGED
|
@@ -658,10 +658,16 @@ implies `--published`. The programmatic equivalents are
|
|
|
658
658
|
`generate({ projectRoot, publishedOnly: true })` and
|
|
659
659
|
`generate({ projectRoot, at: '2026-10-01T12:00:00Z' })`.
|
|
660
660
|
|
|
661
|
+
`--require-status` (`requireStatus: true`) also holds back an entry with no
|
|
662
|
+
workflow status: no meta record, or one without `status`. Use it when every
|
|
663
|
+
entry is expected to carry one, as after a WordPress import. There, a missing
|
|
664
|
+
status means a broken import, not legacy content. It implies `--published`.
|
|
665
|
+
|
|
661
666
|
Astro content collections use the same filter:
|
|
662
667
|
|
|
663
668
|
```ts
|
|
664
669
|
contentrainLoader({ model: 'posts', locale: 'en', publishedOnly: true })
|
|
670
|
+
contentrainLoader({ model: 'posts', locale: 'en', requireStatus: true }) // also hides entries with no status
|
|
665
671
|
```
|
|
666
672
|
|
|
667
673
|
Publication begins exactly at `publish_at` and ends exactly at `expire_at`.
|
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-CvpQ8lJn.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");
|
|
@@ -113,8 +113,8 @@ function resolveEntriesMedia(entries, model, mediaBaseUrl) {
|
|
|
113
113
|
}
|
|
114
114
|
async function entriesOf(ref, model, prefixLocale, publication) {
|
|
115
115
|
const meta = publication ? await require_frontmatter.publicationMeta(ref, model, publication) : void 0;
|
|
116
|
-
const visible = (id) => !publication || require_frontmatter.isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at);
|
|
117
|
-
if (
|
|
116
|
+
const visible = (id) => !publication || require_frontmatter.isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at, publication.requireStatus);
|
|
117
|
+
if (model.kind !== "collection" && !visible()) return [];
|
|
118
118
|
const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : "";
|
|
119
119
|
const withLocale = (data) => ref.locale === null ? data : {
|
|
120
120
|
...data,
|
|
@@ -145,7 +145,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
145
145
|
case "dictionary": {
|
|
146
146
|
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
147
147
|
if (!raw) return [];
|
|
148
|
-
return Object.entries(raw).
|
|
148
|
+
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
149
149
|
id: `${prefix}${key}`,
|
|
150
150
|
data: withLocale({
|
|
151
151
|
key,
|
package/dist/astro/index.d.cts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { t as PublicationOptions } from "../publication-
|
|
1
|
+
import { t as PublicationOptions } from "../publication-CH4OXXzW.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/astro/loader.d.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The slice of Astro's `LoaderContext` this loader uses.
|
|
6
|
+
*
|
|
7
|
+
* Parameter types match Astro's own (5 through 7), and every optional member
|
|
8
|
+
* admits `undefined`: a context type that is narrower than what Astro passes
|
|
9
|
+
* fails `astro check` in a project with `strict` — `create-astro`'s default —
|
|
10
|
+
* or `exactOptionalPropertyTypes`.
|
|
11
|
+
*/
|
|
5
12
|
interface ContentrainLoaderContext {
|
|
6
13
|
store: {
|
|
7
14
|
clear: () => void;
|
|
@@ -20,25 +27,25 @@ interface ContentrainLoaderContext {
|
|
|
20
27
|
logger?: {
|
|
21
28
|
info: (msg: string) => void;
|
|
22
29
|
warn: (msg: string) => void;
|
|
23
|
-
};
|
|
30
|
+
} | undefined;
|
|
24
31
|
/** Astro's resolved config; only `root` is read, to make file paths relative. */
|
|
25
32
|
config?: {
|
|
26
|
-
root?: URL | string;
|
|
27
|
-
};
|
|
28
|
-
generateDigest?: (data: unknown) => string;
|
|
29
|
-
parseData?: (entry: {
|
|
33
|
+
root?: URL | string | undefined;
|
|
34
|
+
} | undefined;
|
|
35
|
+
generateDigest?: ((data: Record<string, unknown> | string) => string) | undefined;
|
|
36
|
+
parseData?: ((entry: {
|
|
30
37
|
id: string;
|
|
31
38
|
data: Record<string, unknown>;
|
|
32
39
|
filePath?: string;
|
|
33
|
-
}) => Promise<Record<string, unknown
|
|
34
|
-
renderMarkdown?: (content: string) => Promise<{
|
|
40
|
+
}) => Promise<Record<string, unknown>>) | undefined;
|
|
41
|
+
renderMarkdown?: ((content: string) => Promise<{
|
|
35
42
|
html: string;
|
|
36
43
|
metadata?: Record<string, unknown>;
|
|
37
|
-
}
|
|
44
|
+
}>) | undefined;
|
|
38
45
|
watcher?: {
|
|
39
46
|
add: (path: string) => void;
|
|
40
47
|
on: (event: string, cb: (path: string) => void) => void;
|
|
41
|
-
};
|
|
48
|
+
} | undefined;
|
|
42
49
|
}
|
|
43
50
|
/** Structurally Astro's `Loader`. */
|
|
44
51
|
interface ContentrainLoader {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":"
|
|
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;;;;AAnBF;;;;;;;;;;;AAiDA;;;;;;iBAAgB,iBAAA,CAAkB,OAAA,EAAS,wBAAA,GAA2B,iBAAA"}
|
package/dist/astro/index.d.mts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { t as PublicationOptions } from "../publication-
|
|
1
|
+
import { t as PublicationOptions } from "../publication-C90PRY0Y.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/astro/loader.d.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The slice of Astro's `LoaderContext` this loader uses.
|
|
6
|
+
*
|
|
7
|
+
* Parameter types match Astro's own (5 through 7), and every optional member
|
|
8
|
+
* admits `undefined`: a context type that is narrower than what Astro passes
|
|
9
|
+
* fails `astro check` in a project with `strict` — `create-astro`'s default —
|
|
10
|
+
* or `exactOptionalPropertyTypes`.
|
|
11
|
+
*/
|
|
5
12
|
interface ContentrainLoaderContext {
|
|
6
13
|
store: {
|
|
7
14
|
clear: () => void;
|
|
@@ -20,25 +27,25 @@ interface ContentrainLoaderContext {
|
|
|
20
27
|
logger?: {
|
|
21
28
|
info: (msg: string) => void;
|
|
22
29
|
warn: (msg: string) => void;
|
|
23
|
-
};
|
|
30
|
+
} | undefined;
|
|
24
31
|
/** Astro's resolved config; only `root` is read, to make file paths relative. */
|
|
25
32
|
config?: {
|
|
26
|
-
root?: URL | string;
|
|
27
|
-
};
|
|
28
|
-
generateDigest?: (data: unknown) => string;
|
|
29
|
-
parseData?: (entry: {
|
|
33
|
+
root?: URL | string | undefined;
|
|
34
|
+
} | undefined;
|
|
35
|
+
generateDigest?: ((data: Record<string, unknown> | string) => string) | undefined;
|
|
36
|
+
parseData?: ((entry: {
|
|
30
37
|
id: string;
|
|
31
38
|
data: Record<string, unknown>;
|
|
32
39
|
filePath?: string;
|
|
33
|
-
}) => Promise<Record<string, unknown
|
|
34
|
-
renderMarkdown?: (content: string) => Promise<{
|
|
40
|
+
}) => Promise<Record<string, unknown>>) | undefined;
|
|
41
|
+
renderMarkdown?: ((content: string) => Promise<{
|
|
35
42
|
html: string;
|
|
36
43
|
metadata?: Record<string, unknown>;
|
|
37
|
-
}
|
|
44
|
+
}>) | undefined;
|
|
38
45
|
watcher?: {
|
|
39
46
|
add: (path: string) => void;
|
|
40
47
|
on: (event: string, cb: (path: string) => void) => void;
|
|
41
|
-
};
|
|
48
|
+
} | undefined;
|
|
42
49
|
}
|
|
43
50
|
/** Structurally Astro's `Loader`. */
|
|
44
51
|
interface ContentrainLoader {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":"
|
|
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;;;;AAnBF;;;;;;;;;;;AAiDA;;;;;;iBAAgB,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 readJson, c as isPublishedAt, l as publicationContext, n as stringLikeFieldKeys, o as readText, r as readProjectManifest, t as parseFrontmatter, u as publicationMeta } from "../frontmatter-
|
|
2
|
+
import { a as readJson, c as isPublishedAt, l as publicationContext, n as stringLikeFieldKeys, o as readText, r as readProjectManifest, t as parseFrontmatter, u as publicationMeta } from "../frontmatter-Bf6Y8vAj.mjs";
|
|
3
3
|
import { rewriteEntryMedia } from "@contentrain/types";
|
|
4
4
|
import { isAbsolute, join, relative } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -112,8 +112,8 @@ function resolveEntriesMedia(entries, model, mediaBaseUrl) {
|
|
|
112
112
|
}
|
|
113
113
|
async function entriesOf(ref, model, prefixLocale, publication) {
|
|
114
114
|
const meta = publication ? await publicationMeta(ref, model, publication) : void 0;
|
|
115
|
-
const visible = (id) => !publication || isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at);
|
|
116
|
-
if (
|
|
115
|
+
const visible = (id) => !publication || isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at, publication.requireStatus);
|
|
116
|
+
if (model.kind !== "collection" && !visible()) return [];
|
|
117
117
|
const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : "";
|
|
118
118
|
const withLocale = (data) => ref.locale === null ? data : {
|
|
119
119
|
...data,
|
|
@@ -144,7 +144,7 @@ async function entriesOf(ref, model, prefixLocale, publication) {
|
|
|
144
144
|
case "dictionary": {
|
|
145
145
|
const raw = await readJson(ref.filePath);
|
|
146
146
|
if (!raw) return [];
|
|
147
|
-
return Object.entries(raw).
|
|
147
|
+
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
148
148
|
id: `${prefix}${key}`,
|
|
149
149
|
data: withLocale({
|
|
150
150
|
key,
|
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/** The slice of Astro's `LoaderContext` this loader uses. */\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 }\n /** Astro's resolved config; only `root` is read, to make file paths relative. */\n config?: { root?: URL | string }\n generateDigest?: (data: unknown) => string\n parseData?: (entry: { id: string, data: Record<string, unknown>, filePath?: string }) => Promise<Record<string, unknown>>\n renderMarkdown?: (content: string) => Promise<{ html: string, metadata?: Record<string, unknown> }>\n watcher?: { add: (path: string) => void, on: (event: string, cb: (path: string) => void) => void }\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)\n if ((model.kind === 'singleton' || model.kind === 'document') && !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 .filter(([id]) => visible(id))\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 } 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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"}
|
package/dist/cli.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_generate = require("./generate-
|
|
2
|
+
const require_generate = require("./generate-BwG58cTu.cjs");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
let node_fs = require("node:fs");
|
|
5
5
|
//#region src/cli.ts
|
|
6
6
|
async function main() {
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
if (args[0] !== "generate" && args.length === 0) {
|
|
9
|
-
console.log("Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>]");
|
|
9
|
+
console.log("Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>] [--require-status]");
|
|
10
10
|
console.log("");
|
|
11
11
|
console.log("Commands:");
|
|
12
12
|
console.log(" generate Generate typed client from .contentrain/ project files");
|
|
@@ -16,6 +16,7 @@ async function main() {
|
|
|
16
16
|
console.log(" --watch Watch for changes and regenerate automatically");
|
|
17
17
|
console.log(" --published Include only published content within its publication window");
|
|
18
18
|
console.log(" --at Reproducible publication timestamp (implies --published)");
|
|
19
|
+
console.log(" --require-status Also hold back entries with no status in meta (implies --published)");
|
|
19
20
|
process.exit(0);
|
|
20
21
|
}
|
|
21
22
|
const command = args[0] === "generate" ? "generate" : args[0];
|
|
@@ -33,7 +34,8 @@ async function main() {
|
|
|
33
34
|
if (atIndex >= 0 && (!at || at.startsWith("--"))) throw new Error("--at requires an ISO timestamp");
|
|
34
35
|
const publication = {
|
|
35
36
|
publishedOnly: args.includes("--published"),
|
|
36
|
-
at
|
|
37
|
+
at,
|
|
38
|
+
requireStatus: args.includes("--require-status")
|
|
37
39
|
};
|
|
38
40
|
const result = await require_generate.generate({
|
|
39
41
|
projectRoot,
|
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as generate } from "./generate-
|
|
2
|
+
import { t as generate } from "./generate-BEfRkd7j.mjs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { watch } from "node:fs";
|
|
5
5
|
//#region src/cli.ts
|
|
6
6
|
async function main() {
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
if (args[0] !== "generate" && args.length === 0) {
|
|
9
|
-
console.log("Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>]");
|
|
9
|
+
console.log("Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>] [--require-status]");
|
|
10
10
|
console.log("");
|
|
11
11
|
console.log("Commands:");
|
|
12
12
|
console.log(" generate Generate typed client from .contentrain/ project files");
|
|
@@ -16,6 +16,7 @@ async function main() {
|
|
|
16
16
|
console.log(" --watch Watch for changes and regenerate automatically");
|
|
17
17
|
console.log(" --published Include only published content within its publication window");
|
|
18
18
|
console.log(" --at Reproducible publication timestamp (implies --published)");
|
|
19
|
+
console.log(" --require-status Also hold back entries with no status in meta (implies --published)");
|
|
19
20
|
process.exit(0);
|
|
20
21
|
}
|
|
21
22
|
const command = args[0] === "generate" ? "generate" : args[0];
|
|
@@ -33,7 +34,8 @@ async function main() {
|
|
|
33
34
|
if (atIndex >= 0 && (!at || at.startsWith("--"))) throw new Error("--at requires an ISO timestamp");
|
|
34
35
|
const publication = {
|
|
35
36
|
publishedOnly: args.includes("--published"),
|
|
36
|
-
at
|
|
37
|
+
at,
|
|
38
|
+
requireStatus: args.includes("--require-status")
|
|
37
39
|
};
|
|
38
40
|
const result = await generate({
|
|
39
41
|
projectRoot,
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { resolve, join } from 'node:path'\nimport { watch } from 'node:fs'\nimport type { PublicationOptions } from './generator/publication.js'\nimport { generate } from './generator/generate.js'\n\nasync function main() {\n const args = process.argv.slice(2)\n\n if (args[0] !== 'generate' && args.length === 0) {\n console.log('Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>]')\n console.log('')\n console.log('Commands:')\n console.log(' generate Generate typed client from .contentrain/ project files')\n console.log('')\n console.log('Options:')\n console.log(' --root Project root directory (default: cwd)')\n console.log(' --watch Watch for changes and regenerate automatically')\n console.log(' --published Include only published content within its publication window')\n console.log(' --at Reproducible publication timestamp (implies --published)')\n process.exit(0)\n }\n\n const command = args[0] === 'generate' ? 'generate' : args[0]\n\n if (command !== 'generate') {\n console.error(`Unknown command: ${command}`)\n process.exit(1)\n }\n\n const rootIdx = args.indexOf('--root')\n const rootArg = rootIdx !== -1 ? args[rootIdx + 1] : undefined\n const projectRoot = rootArg ? resolve(rootArg) : process.cwd()\n const watchMode = args.includes('--watch')\n\n try {\n const atIndex = args.indexOf('--at')\n const at = atIndex < 0 ? undefined : args[atIndex + 1]\n if (atIndex >= 0 && (!at || at.startsWith('--'))) throw new Error('--at requires an ISO timestamp')\n const publication = { publishedOnly: args.includes('--published'), at }\n const result = await generate({ projectRoot, ...publication })\n\n console.log(`@contentrain/query — generated client`)\n console.log(` Models: ${result.typesCount}`)\n console.log(` Data modules: ${result.dataModulesCount}`)\n console.log(` Files: ${result.generatedFiles.length}`)\n if (result.packageJsonUpdated) {\n console.log(` package.json: #contentrain imports added`)\n }\n console.log(` Output: .contentrain/client/`)\n\n if (watchMode) {\n startWatch(projectRoot, publication)\n }\n } catch (err) {\n console.error('Generate failed:', (err as Error).message)\n process.exit(1)\n }\n}\n\nfunction startWatch(projectRoot: string, publication: PublicationOptions) {\n const crDir = join(projectRoot, '.contentrain')\n const modelsDir = join(crDir, 'models')\n const contentDir = join(crDir, 'content')\n const configFile = join(crDir, 'config.json')\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n const regenerate = async () => {\n try {\n const result = await generate({ projectRoot, ...publication })\n console.log(` Regenerated: ${result.typesCount} models, ${result.dataModulesCount} data modules`)\n } catch (err) {\n console.error(' Regenerate failed:', (err as Error).message)\n }\n }\n\n const onChange = (_event: string, filename: string | null) => {\n // Skip changes in the client output directory\n if (filename && filename.startsWith('client')) return\n\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n console.log(` Change detected: ${filename ?? 'unknown'}`)\n regenerate()\n }, 150)\n }\n\n try { watch(modelsDir, { recursive: true }, onChange) } catch { /* dir may not exist */ }\n try { watch(contentDir, { recursive: true }, onChange) } catch { /* dir may not exist */ }\n try { watch(join(crDir, 'meta'), { recursive: true }, onChange) } catch { /* dir may not exist */ }\n try { watch(configFile, onChange) } catch { /* file may not exist */ }\n\n console.log('')\n console.log('Watching for changes in .contentrain/ ...')\n console.log('Press Ctrl+C to stop.')\n}\n\nmain()\n"],"mappings":";;;;;AAOA,eAAe,OAAO;CACpB,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAElC,KAAI,KAAK,OAAO,cAAc,KAAK,WAAW,GAAG;AAC/C,UAAQ,IAAI,
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { resolve, join } from 'node:path'\nimport { watch } from 'node:fs'\nimport type { PublicationOptions } from './generator/publication.js'\nimport { generate } from './generator/generate.js'\n\nasync function main() {\n const args = process.argv.slice(2)\n\n if (args[0] !== 'generate' && args.length === 0) {\n console.log('Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>] [--require-status]')\n console.log('')\n console.log('Commands:')\n console.log(' generate Generate typed client from .contentrain/ project files')\n console.log('')\n console.log('Options:')\n console.log(' --root Project root directory (default: cwd)')\n console.log(' --watch Watch for changes and regenerate automatically')\n console.log(' --published Include only published content within its publication window')\n console.log(' --at Reproducible publication timestamp (implies --published)')\n console.log(' --require-status Also hold back entries with no status in meta (implies --published)')\n process.exit(0)\n }\n\n const command = args[0] === 'generate' ? 'generate' : args[0]\n\n if (command !== 'generate') {\n console.error(`Unknown command: ${command}`)\n process.exit(1)\n }\n\n const rootIdx = args.indexOf('--root')\n const rootArg = rootIdx !== -1 ? args[rootIdx + 1] : undefined\n const projectRoot = rootArg ? resolve(rootArg) : process.cwd()\n const watchMode = args.includes('--watch')\n\n try {\n const atIndex = args.indexOf('--at')\n const at = atIndex < 0 ? undefined : args[atIndex + 1]\n if (atIndex >= 0 && (!at || at.startsWith('--'))) throw new Error('--at requires an ISO timestamp')\n const publication = { publishedOnly: args.includes('--published'), at, requireStatus: args.includes('--require-status') }\n const result = await generate({ projectRoot, ...publication })\n\n console.log(`@contentrain/query — generated client`)\n console.log(` Models: ${result.typesCount}`)\n console.log(` Data modules: ${result.dataModulesCount}`)\n console.log(` Files: ${result.generatedFiles.length}`)\n if (result.packageJsonUpdated) {\n console.log(` package.json: #contentrain imports added`)\n }\n console.log(` Output: .contentrain/client/`)\n\n if (watchMode) {\n startWatch(projectRoot, publication)\n }\n } catch (err) {\n console.error('Generate failed:', (err as Error).message)\n process.exit(1)\n }\n}\n\nfunction startWatch(projectRoot: string, publication: PublicationOptions) {\n const crDir = join(projectRoot, '.contentrain')\n const modelsDir = join(crDir, 'models')\n const contentDir = join(crDir, 'content')\n const configFile = join(crDir, 'config.json')\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n const regenerate = async () => {\n try {\n const result = await generate({ projectRoot, ...publication })\n console.log(` Regenerated: ${result.typesCount} models, ${result.dataModulesCount} data modules`)\n } catch (err) {\n console.error(' Regenerate failed:', (err as Error).message)\n }\n }\n\n const onChange = (_event: string, filename: string | null) => {\n // Skip changes in the client output directory\n if (filename && filename.startsWith('client')) return\n\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n console.log(` Change detected: ${filename ?? 'unknown'}`)\n regenerate()\n }, 150)\n }\n\n try { watch(modelsDir, { recursive: true }, onChange) } catch { /* dir may not exist */ }\n try { watch(contentDir, { recursive: true }, onChange) } catch { /* dir may not exist */ }\n try { watch(join(crDir, 'meta'), { recursive: true }, onChange) } catch { /* dir may not exist */ }\n try { watch(configFile, onChange) } catch { /* file may not exist */ }\n\n console.log('')\n console.log('Watching for changes in .contentrain/ ...')\n console.log('Press Ctrl+C to stop.')\n}\n\nmain()\n"],"mappings":"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"}
|
|
@@ -3,14 +3,15 @@ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
//#region src/generator/publication.ts
|
|
5
5
|
function publicationContext(projectRoot, defaultLocale, options) {
|
|
6
|
-
if (!options.publishedOnly && options.at === void 0) return void 0;
|
|
6
|
+
if (!options.publishedOnly && !options.requireStatus && options.at === void 0) return void 0;
|
|
7
7
|
if (options.at !== void 0 && !/^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/i.test(options.at)) throw new Error("Invalid publication --at timestamp: include an ISO date, time and timezone");
|
|
8
8
|
const at = options.at === void 0 ? Date.now() : Date.parse(options.at);
|
|
9
9
|
if (!Number.isFinite(at)) throw new Error("Invalid publication --at timestamp");
|
|
10
10
|
return {
|
|
11
11
|
projectRoot,
|
|
12
12
|
defaultLocale,
|
|
13
|
-
at
|
|
13
|
+
at,
|
|
14
|
+
requireStatus: options.requireStatus === true
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
/** Canonical meta is independent of custom content paths and locale strategies. */
|
|
@@ -35,11 +36,15 @@ async function publicationMeta(ref, model, context) {
|
|
|
35
36
|
if (!meta || typeof meta !== "object" || Array.isArray(meta)) throw new Error(`Invalid publication metadata: ${path}`);
|
|
36
37
|
return meta;
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Legacy content without meta remains visible, matching Studio's CDN contract —
|
|
41
|
+
* unless `requireStatus`, where an entry without a status (missing or empty) is held back.
|
|
42
|
+
*/
|
|
43
|
+
function isPublishedAt(meta, at, requireStatus = false) {
|
|
44
|
+
if (meta === void 0) return !requireStatus;
|
|
41
45
|
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return false;
|
|
42
46
|
const value = meta;
|
|
47
|
+
if (!value.status && requireStatus) return false;
|
|
43
48
|
if (value.status && value.status !== "published") return false;
|
|
44
49
|
for (const key of ["publish_at", "expire_at"]) {
|
|
45
50
|
if (value[key] === void 0 || value[key] === null) continue;
|
|
@@ -292,4 +297,4 @@ function parseValue(raw, forceString = false) {
|
|
|
292
297
|
//#endregion
|
|
293
298
|
export { readJson as a, isPublishedAt as c, readDir as i, publicationContext as l, stringLikeFieldKeys as n, readText as o, readProjectManifest as r, writeText as s, parseFrontmatter as t, publicationMeta as u };
|
|
294
299
|
|
|
295
|
-
//# sourceMappingURL=frontmatter-
|
|
300
|
+
//# sourceMappingURL=frontmatter-Bf6Y8vAj.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -3,14 +3,15 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
//#region src/generator/publication.ts
|
|
5
5
|
function publicationContext(projectRoot, defaultLocale, options) {
|
|
6
|
-
if (!options.publishedOnly && options.at === void 0) return void 0;
|
|
6
|
+
if (!options.publishedOnly && !options.requireStatus && options.at === void 0) return void 0;
|
|
7
7
|
if (options.at !== void 0 && !/^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/i.test(options.at)) throw new Error("Invalid publication --at timestamp: include an ISO date, time and timezone");
|
|
8
8
|
const at = options.at === void 0 ? Date.now() : Date.parse(options.at);
|
|
9
9
|
if (!Number.isFinite(at)) throw new Error("Invalid publication --at timestamp");
|
|
10
10
|
return {
|
|
11
11
|
projectRoot,
|
|
12
12
|
defaultLocale,
|
|
13
|
-
at
|
|
13
|
+
at,
|
|
14
|
+
requireStatus: options.requireStatus === true
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
/** Canonical meta is independent of custom content paths and locale strategies. */
|
|
@@ -35,11 +36,15 @@ async function publicationMeta(ref, model, context) {
|
|
|
35
36
|
if (!meta || typeof meta !== "object" || Array.isArray(meta)) throw new Error(`Invalid publication metadata: ${path}`);
|
|
36
37
|
return meta;
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Legacy content without meta remains visible, matching Studio's CDN contract —
|
|
41
|
+
* unless `requireStatus`, where an entry without a status (missing or empty) is held back.
|
|
42
|
+
*/
|
|
43
|
+
function isPublishedAt(meta, at, requireStatus = false) {
|
|
44
|
+
if (meta === void 0) return !requireStatus;
|
|
41
45
|
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return false;
|
|
42
46
|
const value = meta;
|
|
47
|
+
if (!value.status && requireStatus) return false;
|
|
43
48
|
if (value.status && value.status !== "published") return false;
|
|
44
49
|
for (const key of ["publish_at", "expire_at"]) {
|
|
45
50
|
if (value[key] === void 0 || value[key] === null) continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as readJson, c as isPublishedAt, i as readDir, l as publicationContext, n as stringLikeFieldKeys, o as readText, r as readProjectManifest, s as writeText, t as parseFrontmatter, u as publicationMeta } from "./frontmatter-
|
|
1
|
+
import { a as readJson, c as isPublishedAt, i as readDir, l as publicationContext, n as stringLikeFieldKeys, o as readText, r as readProjectManifest, s as writeText, t as parseFrontmatter, u as publicationMeta } from "./frontmatter-Bf6Y8vAj.mjs";
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/generator/type-emitter.ts
|
|
@@ -219,8 +219,8 @@ async function emitSingleModule(ref, models, publication) {
|
|
|
219
219
|
const model = models.find((m) => m.id === ref.modelId);
|
|
220
220
|
if (!model) return null;
|
|
221
221
|
const meta = publication ? await publicationMeta(ref, model, publication) : void 0;
|
|
222
|
-
const visible = (id) => !publication || isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at);
|
|
223
|
-
if (
|
|
222
|
+
const visible = (id) => !publication || isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at, publication.requireStatus);
|
|
223
|
+
if (model.kind !== "collection" && !visible()) return null;
|
|
224
224
|
const localeSuffix = ref.locale ? `.${ref.locale}` : "";
|
|
225
225
|
switch (model.kind) {
|
|
226
226
|
case "collection": {
|
|
@@ -243,10 +243,9 @@ async function emitSingleModule(ref, models, publication) {
|
|
|
243
243
|
case "dictionary": {
|
|
244
244
|
const raw = await readJson(ref.filePath);
|
|
245
245
|
if (!raw) return null;
|
|
246
|
-
const entries = Object.fromEntries(Object.entries(raw).filter(([id]) => visible(id)));
|
|
247
246
|
return {
|
|
248
247
|
fileName: `${model.id}${localeSuffix}.mjs`,
|
|
249
|
-
content: `export default ${canonicalStringify(
|
|
248
|
+
content: `export default ${canonicalStringify(raw)}\n`
|
|
250
249
|
};
|
|
251
250
|
}
|
|
252
251
|
case "document": {
|
|
@@ -721,4 +720,4 @@ async function generate(options) {
|
|
|
721
720
|
//#endregion
|
|
722
721
|
export { generate as t };
|
|
723
722
|
|
|
724
|
-
//# sourceMappingURL=generate-
|
|
723
|
+
//# sourceMappingURL=generate-BEfRkd7j.mjs.map
|
|
@@ -0,0 +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,4 +1,4 @@
|
|
|
1
|
-
const require_frontmatter = require("./frontmatter-
|
|
1
|
+
const require_frontmatter = require("./frontmatter-CvpQ8lJn.cjs");
|
|
2
2
|
let node_fs_promises = require("node:fs/promises");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
//#region src/generator/type-emitter.ts
|
|
@@ -219,8 +219,8 @@ async function emitSingleModule(ref, models, publication) {
|
|
|
219
219
|
const model = models.find((m) => m.id === ref.modelId);
|
|
220
220
|
if (!model) return null;
|
|
221
221
|
const meta = publication ? await require_frontmatter.publicationMeta(ref, model, publication) : void 0;
|
|
222
|
-
const visible = (id) => !publication || require_frontmatter.isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at);
|
|
223
|
-
if (
|
|
222
|
+
const visible = (id) => !publication || require_frontmatter.isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at, publication.requireStatus);
|
|
223
|
+
if (model.kind !== "collection" && !visible()) return null;
|
|
224
224
|
const localeSuffix = ref.locale ? `.${ref.locale}` : "";
|
|
225
225
|
switch (model.kind) {
|
|
226
226
|
case "collection": {
|
|
@@ -243,10 +243,9 @@ async function emitSingleModule(ref, models, publication) {
|
|
|
243
243
|
case "dictionary": {
|
|
244
244
|
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
245
245
|
if (!raw) return null;
|
|
246
|
-
const entries = Object.fromEntries(Object.entries(raw).filter(([id]) => visible(id)));
|
|
247
246
|
return {
|
|
248
247
|
fileName: `${model.id}${localeSuffix}.mjs`,
|
|
249
|
-
content: `export default ${canonicalStringify(
|
|
248
|
+
content: `export default ${canonicalStringify(raw)}\n`
|
|
250
249
|
};
|
|
251
250
|
}
|
|
252
251
|
case "document": {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as generate } from "../generate-
|
|
1
|
+
import { t as generate } from "../generate-BEfRkd7j.mjs";
|
|
2
2
|
export { generate };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/generator/publication.d.ts
|
|
2
|
+
interface PublicationOptions {
|
|
3
|
+
/** Opt in for public builds; the default keeps the existing editorial client. */
|
|
4
|
+
publishedOnly?: boolean;
|
|
5
|
+
/** ISO timestamp for a reproducible public build. Implies publishedOnly. */
|
|
6
|
+
at?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Hold back an entry that carries no workflow status — no meta record, or a
|
|
9
|
+
* record without `status`. Without it such an entry stays visible (legacy
|
|
10
|
+
* content predates meta). Set it when the producer writes a status for every
|
|
11
|
+
* entry, as the WordPress import does: a missing status then means a broken
|
|
12
|
+
* import, and a public build must not guess. Implies publishedOnly.
|
|
13
|
+
*/
|
|
14
|
+
requireStatus?: boolean;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { PublicationOptions as t };
|
|
18
|
+
//# sourceMappingURL=publication-C90PRY0Y.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publication-C90PRY0Y.d.mts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";UAKiB,kBAAA;EAAA;EAEf,aAAA;;EAEA,EAAA;EAFA;;;;;;;EAUA,aAAA;AAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/generator/publication.d.ts
|
|
2
|
+
interface PublicationOptions {
|
|
3
|
+
/** Opt in for public builds; the default keeps the existing editorial client. */
|
|
4
|
+
publishedOnly?: boolean;
|
|
5
|
+
/** ISO timestamp for a reproducible public build. Implies publishedOnly. */
|
|
6
|
+
at?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Hold back an entry that carries no workflow status — no meta record, or a
|
|
9
|
+
* record without `status`. Without it such an entry stays visible (legacy
|
|
10
|
+
* content predates meta). Set it when the producer writes a status for every
|
|
11
|
+
* entry, as the WordPress import does: a missing status then means a broken
|
|
12
|
+
* import, and a public build must not guess. Implies publishedOnly.
|
|
13
|
+
*/
|
|
14
|
+
requireStatus?: boolean;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { PublicationOptions as t };
|
|
18
|
+
//# sourceMappingURL=publication-CH4OXXzW.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publication-CH4OXXzW.d.cts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";UAKiB,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.
|
|
3
|
+
"version": "7.6.1",
|
|
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.24.0"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@types/node": "^22.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter-D9yVqC5y.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\nexport interface PublicationContext {\n projectRoot: string\n defaultLocale: string\n at: number\n}\n\nexport function publicationContext(projectRoot: string, defaultLocale: string, options: PublicationOptions): PublicationContext | undefined {\n if (!options.publishedOnly && 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 }\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/** Legacy content without meta remains visible, matching Studio's CDN contract. */\nexport function isPublishedAt(meta: unknown, at: number): boolean {\n if (meta === undefined) return true\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) return false\n const value = meta as Record<string, unknown>\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":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-BOi6o7Z5.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)\n if ((model.kind === 'singleton' || model.kind === 'document') && !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 const entries = Object.fromEntries(Object.entries(raw).filter(([id]) => visible(id)))\n return { fileName: `${model.id}${localeSuffix}.mjs`, content: `export default ${canonicalStringify(entries)}\\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,10 +0,0 @@
|
|
|
1
|
-
//#region src/generator/publication.d.ts
|
|
2
|
-
interface PublicationOptions {
|
|
3
|
-
/** Opt in for public builds; the default keeps the existing editorial client. */
|
|
4
|
-
publishedOnly?: boolean;
|
|
5
|
-
/** ISO timestamp for a reproducible public build. Implies publishedOnly. */
|
|
6
|
-
at?: string;
|
|
7
|
-
}
|
|
8
|
-
//#endregion
|
|
9
|
-
export { PublicationOptions as t };
|
|
10
|
-
//# sourceMappingURL=publication-CMrWLPi7.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"publication-CMrWLPi7.d.mts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";UAKiB,kBAAA;;EAEf,aAAA;EAEE;EAAF,EAAA;AAAA"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
//#region src/generator/publication.d.ts
|
|
2
|
-
interface PublicationOptions {
|
|
3
|
-
/** Opt in for public builds; the default keeps the existing editorial client. */
|
|
4
|
-
publishedOnly?: boolean;
|
|
5
|
-
/** ISO timestamp for a reproducible public build. Implies publishedOnly. */
|
|
6
|
-
at?: string;
|
|
7
|
-
}
|
|
8
|
-
//#endregion
|
|
9
|
-
export { PublicationOptions as t };
|
|
10
|
-
//# sourceMappingURL=publication-DW0WaAuC.d.cts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"publication-DW0WaAuC.d.cts","names":[],"sources":["../src/generator/publication.ts"],"mappings":";UAKiB,kBAAA;;EAEf,aAAA;EAEE;EAAF,EAAA;AAAA"}
|