@contentrain/query 7.2.3 → 7.3.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 +27 -0
- package/dist/astro/index.cjs +12 -6
- package/dist/astro/index.d.cts +3 -1
- package/dist/astro/index.d.cts.map +1 -1
- package/dist/astro/index.d.mts +3 -1
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +12 -6
- package/dist/astro/index.mjs.map +1 -1
- package/dist/cdn/index.d.cts +1 -1
- package/dist/cdn/index.d.mts +1 -1
- package/dist/cli.cjs +24 -6
- package/dist/cli.mjs +24 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/{frontmatter-DSFLOTwS.cjs → frontmatter-BxZi5QMk.cjs} +68 -1
- package/dist/{frontmatter-CVjPyhOq.mjs → frontmatter-D-QXY_WH.mjs} +52 -3
- package/dist/frontmatter-D-QXY_WH.mjs.map +1 -0
- package/dist/{generate-B7jooE0f.mjs → generate-CT6gqQfR.mjs} +13 -9
- package/dist/generate-CT6gqQfR.mjs.map +1 -0
- package/dist/{generate-D3C6itur.cjs → generate-D0yAmE56.cjs} +12 -8
- package/dist/generator/generate.cjs +1 -1
- package/dist/generator/generate.d.cts +3 -1
- package/dist/generator/generate.d.cts.map +1 -1
- package/dist/generator/generate.d.mts +3 -1
- package/dist/generator/generate.d.mts.map +1 -1
- package/dist/generator/generate.mjs +1 -1
- package/dist/{index-Cjj6FPwV.d.cts → index-D-hSDfue.d.cts} +1 -1
- package/dist/{index-Cjj6FPwV.d.cts.map → index-D-hSDfue.d.cts.map} +1 -1
- package/dist/{index-hug7w9_n.d.mts → index-DWpmPtUW.d.mts} +1 -1
- package/dist/{index-hug7w9_n.d.mts.map → index-DWpmPtUW.d.mts.map} +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/publication-Ba8bLPAO.d.mts +10 -0
- package/dist/publication-Ba8bLPAO.d.mts.map +1 -0
- package/dist/publication-DW0WaAuC.d.cts +10 -0
- package/dist/publication-DW0WaAuC.d.cts.map +1 -0
- package/package.json +2 -2
- package/dist/frontmatter-CVjPyhOq.mjs.map +0 -1
- package/dist/generate-B7jooE0f.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -581,3 +581,30 @@ Full documentation at **[ai.contentrain.io/packages/sdk](https://ai.contentrain.
|
|
|
581
581
|
## 📄 License
|
|
582
582
|
|
|
583
583
|
MIT
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
### Public builds and scheduled publication
|
|
587
|
+
|
|
588
|
+
Editorial generation keeps all content by default. For a deployed static site,
|
|
589
|
+
use `contentrain-query generate --published` (or `contentrain generate --published`).
|
|
590
|
+
Only published entries inside their `publish_at`/`expire_at` window are included.
|
|
591
|
+
Legacy entries without metadata remain visible, matching Studio CDN behavior.
|
|
592
|
+
Invalid schedule values are excluded; malformed metadata stops the build.
|
|
593
|
+
|
|
594
|
+
`--at 2026-10-01T12:00:00Z` fixes the clock for a reproducible public build and
|
|
595
|
+
implies `--published`. The programmatic equivalents are
|
|
596
|
+
`generate({ projectRoot, publishedOnly: true })` and
|
|
597
|
+
`generate({ projectRoot, at: '2026-10-01T12:00:00Z' })`.
|
|
598
|
+
|
|
599
|
+
Astro content collections use the same filter:
|
|
600
|
+
|
|
601
|
+
```ts
|
|
602
|
+
contentrainLoader({ model: 'posts', locale: 'en', publishedOnly: true })
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Publication begins exactly at `publish_at` and ends exactly at `expire_at`.
|
|
606
|
+
All four model kinds read canonical `.contentrain/meta` files, including content
|
|
607
|
+
stored at custom paths. Metadata is not copied into public content fields.
|
|
608
|
+
Watch mode reacts to metadata edits. Time passing alone does not redeploy a
|
|
609
|
+
static site: schedule a build and deployment at each deadline, with retries.
|
|
610
|
+
Use a fixed `at` for testing/reproduction, not a permanent deployment clock.
|
package/dist/astro/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_frontmatter = require("../frontmatter-
|
|
2
|
+
const require_frontmatter = require("../frontmatter-BxZi5QMk.cjs");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
let node_url = require("node:url");
|
|
5
5
|
//#region src/astro/loader.ts
|
|
@@ -38,7 +38,8 @@ function contentrainLoader(options) {
|
|
|
38
38
|
const refs = manifest.contentFiles.filter((ref) => ref.modelId === model.id && (options.locale === void 0 || ref.locale === options.locale));
|
|
39
39
|
if (options.locale !== void 0 && refs.length === 0 && model.i18n) context.logger?.warn(`contentrainLoader: model "${model.id}" has no content for locale "${options.locale}" — collection is empty.`);
|
|
40
40
|
const prefixLocale = model.i18n === true && options.locale === void 0;
|
|
41
|
-
const
|
|
41
|
+
const publication = require_frontmatter.publicationContext(root, manifest.config.locales.default, options);
|
|
42
|
+
const entries = (await Promise.all(refs.map((ref) => entriesOf(ref, model, prefixLocale, publication)))).flat();
|
|
42
43
|
const siteRoot = toPath(context.config?.root);
|
|
43
44
|
context.store.clear();
|
|
44
45
|
for (const entry of entries) {
|
|
@@ -62,9 +63,11 @@ function contentrainLoader(options) {
|
|
|
62
63
|
watching = true;
|
|
63
64
|
const paths = new Set(refs.map((ref) => ref.filePath));
|
|
64
65
|
for (const path of paths) context.watcher.add(path);
|
|
66
|
+
const metaDir = (0, node_path.join)(root, ".contentrain", "meta", model.id);
|
|
67
|
+
context.watcher.add(metaDir);
|
|
65
68
|
context.watcher.add((0, node_path.join)(root, ".contentrain", "models", `${model.id}.json`));
|
|
66
69
|
const reload = (path) => {
|
|
67
|
-
if (!paths.has(path) && !path.endsWith(`${model.id}.json`)) return;
|
|
70
|
+
if (!paths.has(path) && !path.startsWith(metaDir + "/") && !path.endsWith(`${model.id}.json`)) return;
|
|
68
71
|
this.load(context);
|
|
69
72
|
};
|
|
70
73
|
context.watcher.on("change", reload);
|
|
@@ -80,7 +83,10 @@ function toPath(root) {
|
|
|
80
83
|
if (typeof root !== "string") return (0, node_url.fileURLToPath)(root);
|
|
81
84
|
return root.startsWith("file:") ? (0, node_url.fileURLToPath)(root) : root;
|
|
82
85
|
}
|
|
83
|
-
async function entriesOf(ref, model, prefixLocale) {
|
|
86
|
+
async function entriesOf(ref, model, prefixLocale, publication) {
|
|
87
|
+
const meta = publication ? await require_frontmatter.publicationMeta(ref, model, publication) : void 0;
|
|
88
|
+
const visible = (id) => !publication || require_frontmatter.isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at);
|
|
89
|
+
if ((model.kind === "singleton" || model.kind === "document") && !visible()) return [];
|
|
84
90
|
const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : "";
|
|
85
91
|
const withLocale = (data) => ref.locale === null ? data : {
|
|
86
92
|
...data,
|
|
@@ -90,7 +96,7 @@ async function entriesOf(ref, model, prefixLocale) {
|
|
|
90
96
|
case "collection": {
|
|
91
97
|
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
92
98
|
if (!raw) return [];
|
|
93
|
-
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
99
|
+
return Object.entries(raw).filter(([id]) => visible(id)).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
94
100
|
id: `${prefix}${id}`,
|
|
95
101
|
data: withLocale({
|
|
96
102
|
id,
|
|
@@ -111,7 +117,7 @@ async function entriesOf(ref, model, prefixLocale) {
|
|
|
111
117
|
case "dictionary": {
|
|
112
118
|
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
113
119
|
if (!raw) return [];
|
|
114
|
-
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
120
|
+
return Object.entries(raw).filter(([id]) => visible(id)).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
115
121
|
id: `${prefix}${key}`,
|
|
116
122
|
data: withLocale({
|
|
117
123
|
key,
|
package/dist/astro/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { t as PublicationOptions } from "../publication-DW0WaAuC.cjs";
|
|
2
|
+
|
|
1
3
|
//#region src/astro/loader.d.ts
|
|
2
4
|
/** The slice of Astro's `LoaderContext` this loader uses. */
|
|
3
5
|
interface ContentrainLoaderContext {
|
|
@@ -43,7 +45,7 @@ interface ContentrainLoader {
|
|
|
43
45
|
name: string;
|
|
44
46
|
load: (context: ContentrainLoaderContext) => Promise<void>;
|
|
45
47
|
}
|
|
46
|
-
interface ContentrainLoaderOptions {
|
|
48
|
+
interface ContentrainLoaderOptions extends PublicationOptions {
|
|
47
49
|
/** Model id — the `id` in `.contentrain/models/<id>.json`. */
|
|
48
50
|
model: string;
|
|
49
51
|
/** Project root holding `.contentrain`. Default: `process.cwd()`. */
|
|
@@ -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":";;;;UAuBiB,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;EAPrC;EASV,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,IAAkB,IAAA;EAClB,SAAA,IAAa,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EACjG,cAAA,IAAkB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EACzE,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;EAXyC;EAazF,KAAA;EAZA;EAcA,IAAA;EAdsC;;;;;EAoBtC,MAAA;AAAA;;;;;;;AAfF;;;;;;;;;;;AAKA;;;iBAwCgB,iBAAA,CAAkB,OAAA,EAAS,wBAAA,GAA2B,iBAAA"}
|
package/dist/astro/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { t as PublicationOptions } from "../publication-Ba8bLPAO.mjs";
|
|
2
|
+
|
|
1
3
|
//#region src/astro/loader.d.ts
|
|
2
4
|
/** The slice of Astro's `LoaderContext` this loader uses. */
|
|
3
5
|
interface ContentrainLoaderContext {
|
|
@@ -43,7 +45,7 @@ interface ContentrainLoader {
|
|
|
43
45
|
name: string;
|
|
44
46
|
load: (context: ContentrainLoaderContext) => Promise<void>;
|
|
45
47
|
}
|
|
46
|
-
interface ContentrainLoaderOptions {
|
|
48
|
+
interface ContentrainLoaderOptions extends PublicationOptions {
|
|
47
49
|
/** Model id — the `id` in `.contentrain/models/<id>.json`. */
|
|
48
50
|
model: string;
|
|
49
51
|
/** Project root holding `.contentrain`. Default: `process.cwd()`. */
|
|
@@ -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":";;;;UAuBiB,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;EAPrC;EASV,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,IAAkB,IAAA;EAClB,SAAA,IAAa,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EACjG,cAAA,IAAkB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EACzE,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;EAXyC;EAazF,KAAA;EAZA;EAcA,IAAA;EAdsC;;;;;EAoBtC,MAAA;AAAA;;;;;;;AAfF;;;;;;;;;;;AAKA;;;iBAwCgB,iBAAA,CAAkB,OAAA,EAAS,wBAAA,GAA2B,iBAAA"}
|
package/dist/astro/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as readJson, n as stringLikeFieldKeys, o as readText, r as readProjectManifest, t as parseFrontmatter } from "../frontmatter-
|
|
1
|
+
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-D-QXY_WH.mjs";
|
|
2
2
|
import { isAbsolute, join, relative } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
//#region src/astro/loader.ts
|
|
@@ -37,7 +37,8 @@ function contentrainLoader(options) {
|
|
|
37
37
|
const refs = manifest.contentFiles.filter((ref) => ref.modelId === model.id && (options.locale === void 0 || ref.locale === options.locale));
|
|
38
38
|
if (options.locale !== void 0 && refs.length === 0 && model.i18n) context.logger?.warn(`contentrainLoader: model "${model.id}" has no content for locale "${options.locale}" — collection is empty.`);
|
|
39
39
|
const prefixLocale = model.i18n === true && options.locale === void 0;
|
|
40
|
-
const
|
|
40
|
+
const publication = publicationContext(root, manifest.config.locales.default, options);
|
|
41
|
+
const entries = (await Promise.all(refs.map((ref) => entriesOf(ref, model, prefixLocale, publication)))).flat();
|
|
41
42
|
const siteRoot = toPath(context.config?.root);
|
|
42
43
|
context.store.clear();
|
|
43
44
|
for (const entry of entries) {
|
|
@@ -61,9 +62,11 @@ function contentrainLoader(options) {
|
|
|
61
62
|
watching = true;
|
|
62
63
|
const paths = new Set(refs.map((ref) => ref.filePath));
|
|
63
64
|
for (const path of paths) context.watcher.add(path);
|
|
65
|
+
const metaDir = join(root, ".contentrain", "meta", model.id);
|
|
66
|
+
context.watcher.add(metaDir);
|
|
64
67
|
context.watcher.add(join(root, ".contentrain", "models", `${model.id}.json`));
|
|
65
68
|
const reload = (path) => {
|
|
66
|
-
if (!paths.has(path) && !path.endsWith(`${model.id}.json`)) return;
|
|
69
|
+
if (!paths.has(path) && !path.startsWith(metaDir + "/") && !path.endsWith(`${model.id}.json`)) return;
|
|
67
70
|
this.load(context);
|
|
68
71
|
};
|
|
69
72
|
context.watcher.on("change", reload);
|
|
@@ -79,7 +82,10 @@ function toPath(root) {
|
|
|
79
82
|
if (typeof root !== "string") return fileURLToPath(root);
|
|
80
83
|
return root.startsWith("file:") ? fileURLToPath(root) : root;
|
|
81
84
|
}
|
|
82
|
-
async function entriesOf(ref, model, prefixLocale) {
|
|
85
|
+
async function entriesOf(ref, model, prefixLocale, publication) {
|
|
86
|
+
const meta = publication ? await publicationMeta(ref, model, publication) : void 0;
|
|
87
|
+
const visible = (id) => !publication || isPublishedAt(id === void 0 ? meta : meta?.[id], publication.at);
|
|
88
|
+
if ((model.kind === "singleton" || model.kind === "document") && !visible()) return [];
|
|
83
89
|
const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : "";
|
|
84
90
|
const withLocale = (data) => ref.locale === null ? data : {
|
|
85
91
|
...data,
|
|
@@ -89,7 +95,7 @@ async function entriesOf(ref, model, prefixLocale) {
|
|
|
89
95
|
case "collection": {
|
|
90
96
|
const raw = await readJson(ref.filePath);
|
|
91
97
|
if (!raw) return [];
|
|
92
|
-
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
98
|
+
return Object.entries(raw).filter(([id]) => visible(id)).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
93
99
|
id: `${prefix}${id}`,
|
|
94
100
|
data: withLocale({
|
|
95
101
|
id,
|
|
@@ -110,7 +116,7 @@ async function entriesOf(ref, model, prefixLocale) {
|
|
|
110
116
|
case "dictionary": {
|
|
111
117
|
const raw = await readJson(ref.filePath);
|
|
112
118
|
if (!raw) return [];
|
|
113
|
-
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
119
|
+
return Object.entries(raw).filter(([id]) => visible(id)).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
114
120
|
id: `${prefix}${key}`,
|
|
115
121
|
data: withLocale({
|
|
116
122
|
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 type { ModelDefinition } 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'\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 {\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\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 perFile = await Promise.all(refs.map(ref => entriesOf(ref, model, prefixLocale)))\n const entries = 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 context.watcher.add(join(root, '.contentrain', 'models', `${model.id}.json`))\n const reload = (path: string) => {\n if (!paths.has(path) && !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\nasync function entriesOf(ref: ContentFileRef, model: ModelDefinition, prefixLocale: boolean): Promise<Entry[]> {\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 .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([id, fields]) => ({\n id: `${prefix}${id}`,\n data: withLocale({ id, ...fields }),\n filePath: ref.filePath,\n }))\n }\n\n case 'singleton': {\n const raw = await readJson<Record<string, unknown>>(ref.filePath)\n if (!raw) return []\n return [{\n id: prefix ? `${ref.locale}` : model.id,\n data: withLocale(raw),\n filePath: ref.filePath,\n }]\n }\n\n case 'dictionary': {\n // One entry per key: a dictionary key is a semantic address, and a single\n // blob entry would make getEntry() and reference() useless on it.\n const raw = await readJson<Record<string, string>>(ref.filePath)\n if (!raw) return []\n return Object.entries(raw)\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([key, value]) => ({\n id: `${prefix}${key}`,\n data: withLocale({ key, value }),\n filePath: ref.filePath,\n }))\n }\n\n case 'document': {\n const text = await readText(ref.filePath)\n if (text === null) return []\n const { frontmatter, body } = parseFrontmatter(text, stringLikeFieldKeys(model))\n const slug = ref.slug ?? model.id\n return [{\n id: `${prefix}${slug}`,\n data: withLocale({ slug, ...frontmatter }),\n body,\n filePath: ref.filePath,\n }]\n }\n\n default:\n return []\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/astro/loader.ts"],"sourcesContent":["// Astro content-layer loader for a `.contentrain` project.\n//\n// `contentrain generate` produces a typed client for application code; Astro\n// wants its content in the content layer instead, so pages can query it with\n// `getCollection()` and render documents with `<Content />`. This loader reads\n// the same `.contentrain` files the generator reads — through the same manifest\n// — so a project never has two answers about what its content is.\n//\n// Astro is NOT a dependency here, not even a peer: the returned object is\n// structurally what `defineCollection({ loader })` accepts, and the context is\n// typed by the small surface this loader actually uses. That keeps\n// @contentrain/query dependency-free and usable from any Astro 5 version.\n\nimport { publicationContext, publicationMeta, isPublishedAt, type PublicationOptions, type PublicationContext } from '../generator/publication.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport { 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'\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\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 = 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\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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"}
|
package/dist/cdn/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as FormsClient, C as CommentsClientConfig, D as FormFieldError, E as FormFieldConfig, F as MediaManifest, G as TransportConfig, I as CdnDocumentQuery, J as DocumentDataSource, K as CollectionDataSource, L as CdnDictionaryAccessor, M as MediaAccessor, N as MediaAsset, O as FormSubmitOptions, P as MediaAssetMeta, R as CdnSingletonAccessor, S as CommentsClient, T as FormConfig, W as HttpTransport, X as SingletonDataSource, Y as DocumentIndexEntry, _ as CommentSubmitResult, a as ConversationClient, b as CommentThreadQuery, c as ConversationHistory, d as ConversationSendOptions, f as ConversationToolResult, g as CommentSubmission, h as CommentFieldError, i as ContentrainError, j as FormsClientConfig, k as FormSubmitResult, l as ConversationMessage, m as CommentAuthor, n as ContentrainCDNConfig, o as ConversationClientConfig, p as ConversationUsage, q as DictionaryDataSource, r as createContentrain, s as ConversationContext, t as ContentrainCDNClient, u as ConversationResponse, v as CommentThread, w as PublicComment, x as CommentType, y as CommentThreadConfig, z as CdnCollectionQuery } from "../index-
|
|
1
|
+
import { A as FormsClient, C as CommentsClientConfig, D as FormFieldError, E as FormFieldConfig, F as MediaManifest, G as TransportConfig, I as CdnDocumentQuery, J as DocumentDataSource, K as CollectionDataSource, L as CdnDictionaryAccessor, M as MediaAccessor, N as MediaAsset, O as FormSubmitOptions, P as MediaAssetMeta, R as CdnSingletonAccessor, S as CommentsClient, T as FormConfig, W as HttpTransport, X as SingletonDataSource, Y as DocumentIndexEntry, _ as CommentSubmitResult, a as ConversationClient, b as CommentThreadQuery, c as ConversationHistory, d as ConversationSendOptions, f as ConversationToolResult, g as CommentSubmission, h as CommentFieldError, i as ContentrainError, j as FormsClientConfig, k as FormSubmitResult, l as ConversationMessage, m as CommentAuthor, n as ContentrainCDNConfig, o as ConversationClientConfig, p as ConversationUsage, q as DictionaryDataSource, r as createContentrain, s as ConversationContext, t as ContentrainCDNClient, u as ConversationResponse, v as CommentThread, w as PublicComment, x as CommentType, y as CommentThreadConfig, z as CdnCollectionQuery } from "../index-D-hSDfue.cjs";
|
|
2
2
|
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, CollectionDataSource, CommentAuthor, CommentFieldError, CommentSubmission, CommentSubmitResult, CommentThread, CommentThreadConfig, CommentThreadQuery, CommentType, CommentsClient, CommentsClientConfig, ContentrainCDNClient, ContentrainCDNConfig, ContentrainError, ConversationClient, ConversationClientConfig, ConversationContext, ConversationHistory, ConversationMessage, ConversationResponse, ConversationSendOptions, ConversationToolResult, ConversationUsage, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry, FormConfig, FormFieldConfig, FormFieldError, FormSubmitOptions, FormSubmitResult, FormsClient, FormsClientConfig, HttpTransport, MediaAccessor, MediaAsset, MediaAssetMeta, MediaManifest, PublicComment, SingletonDataSource, TransportConfig, createContentrain };
|
package/dist/cdn/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as FormsClient, C as CommentsClientConfig, D as FormFieldError, E as FormFieldConfig, F as MediaManifest, G as TransportConfig, I as CdnDocumentQuery, J as DocumentDataSource, K as CollectionDataSource, L as CdnDictionaryAccessor, M as MediaAccessor, N as MediaAsset, O as FormSubmitOptions, P as MediaAssetMeta, R as CdnSingletonAccessor, S as CommentsClient, T as FormConfig, W as HttpTransport, X as SingletonDataSource, Y as DocumentIndexEntry, _ as CommentSubmitResult, a as ConversationClient, b as CommentThreadQuery, c as ConversationHistory, d as ConversationSendOptions, f as ConversationToolResult, g as CommentSubmission, h as CommentFieldError, i as ContentrainError, j as FormsClientConfig, k as FormSubmitResult, l as ConversationMessage, m as CommentAuthor, n as ContentrainCDNConfig, o as ConversationClientConfig, p as ConversationUsage, q as DictionaryDataSource, r as createContentrain, s as ConversationContext, t as ContentrainCDNClient, u as ConversationResponse, v as CommentThread, w as PublicComment, x as CommentType, y as CommentThreadConfig, z as CdnCollectionQuery } from "../index-
|
|
1
|
+
import { A as FormsClient, C as CommentsClientConfig, D as FormFieldError, E as FormFieldConfig, F as MediaManifest, G as TransportConfig, I as CdnDocumentQuery, J as DocumentDataSource, K as CollectionDataSource, L as CdnDictionaryAccessor, M as MediaAccessor, N as MediaAsset, O as FormSubmitOptions, P as MediaAssetMeta, R as CdnSingletonAccessor, S as CommentsClient, T as FormConfig, W as HttpTransport, X as SingletonDataSource, Y as DocumentIndexEntry, _ as CommentSubmitResult, a as ConversationClient, b as CommentThreadQuery, c as ConversationHistory, d as ConversationSendOptions, f as ConversationToolResult, g as CommentSubmission, h as CommentFieldError, i as ContentrainError, j as FormsClientConfig, k as FormSubmitResult, l as ConversationMessage, m as CommentAuthor, n as ContentrainCDNConfig, o as ConversationClientConfig, p as ConversationUsage, q as DictionaryDataSource, r as createContentrain, s as ConversationContext, t as ContentrainCDNClient, u as ConversationResponse, v as CommentThread, w as PublicComment, x as CommentType, y as CommentThreadConfig, z as CdnCollectionQuery } from "../index-DWpmPtUW.mjs";
|
|
2
2
|
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, CollectionDataSource, CommentAuthor, CommentFieldError, CommentSubmission, CommentSubmitResult, CommentThread, CommentThreadConfig, CommentThreadQuery, CommentType, CommentsClient, CommentsClientConfig, ContentrainCDNClient, ContentrainCDNConfig, ContentrainError, ConversationClient, ConversationClientConfig, ConversationContext, ConversationHistory, ConversationMessage, ConversationResponse, ConversationSendOptions, ConversationToolResult, ConversationUsage, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry, FormConfig, FormFieldConfig, FormFieldError, FormSubmitOptions, FormSubmitResult, FormsClient, FormsClientConfig, HttpTransport, MediaAccessor, MediaAsset, MediaAssetMeta, MediaManifest, PublicComment, SingletonDataSource, TransportConfig, createContentrain };
|
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-D0yAmE56.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]");
|
|
9
|
+
console.log("Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>]");
|
|
10
10
|
console.log("");
|
|
11
11
|
console.log("Commands:");
|
|
12
12
|
console.log(" generate Generate typed client from .contentrain/ project files");
|
|
@@ -14,6 +14,8 @@ async function main() {
|
|
|
14
14
|
console.log("Options:");
|
|
15
15
|
console.log(" --root Project root directory (default: cwd)");
|
|
16
16
|
console.log(" --watch Watch for changes and regenerate automatically");
|
|
17
|
+
console.log(" --published Include only published content within its publication window");
|
|
18
|
+
console.log(" --at Reproducible publication timestamp (implies --published)");
|
|
17
19
|
process.exit(0);
|
|
18
20
|
}
|
|
19
21
|
const command = args[0] === "generate" ? "generate" : args[0];
|
|
@@ -26,20 +28,30 @@ async function main() {
|
|
|
26
28
|
const projectRoot = rootArg ? (0, node_path.resolve)(rootArg) : process.cwd();
|
|
27
29
|
const watchMode = args.includes("--watch");
|
|
28
30
|
try {
|
|
29
|
-
const
|
|
31
|
+
const atIndex = args.indexOf("--at");
|
|
32
|
+
const at = atIndex < 0 ? void 0 : args[atIndex + 1];
|
|
33
|
+
if (atIndex >= 0 && (!at || at.startsWith("--"))) throw new Error("--at requires an ISO timestamp");
|
|
34
|
+
const publication = {
|
|
35
|
+
publishedOnly: args.includes("--published"),
|
|
36
|
+
at
|
|
37
|
+
};
|
|
38
|
+
const result = await require_generate.generate({
|
|
39
|
+
projectRoot,
|
|
40
|
+
...publication
|
|
41
|
+
});
|
|
30
42
|
console.log(`@contentrain/query — generated client`);
|
|
31
43
|
console.log(` Models: ${result.typesCount}`);
|
|
32
44
|
console.log(` Data modules: ${result.dataModulesCount}`);
|
|
33
45
|
console.log(` Files: ${result.generatedFiles.length}`);
|
|
34
46
|
if (result.packageJsonUpdated) console.log(` package.json: #contentrain imports added`);
|
|
35
47
|
console.log(` Output: .contentrain/client/`);
|
|
36
|
-
if (watchMode) startWatch(projectRoot);
|
|
48
|
+
if (watchMode) startWatch(projectRoot, publication);
|
|
37
49
|
} catch (err) {
|
|
38
50
|
console.error("Generate failed:", err.message);
|
|
39
51
|
process.exit(1);
|
|
40
52
|
}
|
|
41
53
|
}
|
|
42
|
-
function startWatch(projectRoot) {
|
|
54
|
+
function startWatch(projectRoot, publication) {
|
|
43
55
|
const crDir = (0, node_path.join)(projectRoot, ".contentrain");
|
|
44
56
|
const modelsDir = (0, node_path.join)(crDir, "models");
|
|
45
57
|
const contentDir = (0, node_path.join)(crDir, "content");
|
|
@@ -47,7 +59,10 @@ function startWatch(projectRoot) {
|
|
|
47
59
|
let debounceTimer = null;
|
|
48
60
|
const regenerate = async () => {
|
|
49
61
|
try {
|
|
50
|
-
const result = await require_generate.generate({
|
|
62
|
+
const result = await require_generate.generate({
|
|
63
|
+
projectRoot,
|
|
64
|
+
...publication
|
|
65
|
+
});
|
|
51
66
|
console.log(` Regenerated: ${result.typesCount} models, ${result.dataModulesCount} data modules`);
|
|
52
67
|
} catch (err) {
|
|
53
68
|
console.error(" Regenerate failed:", err.message);
|
|
@@ -67,6 +82,9 @@ function startWatch(projectRoot) {
|
|
|
67
82
|
try {
|
|
68
83
|
(0, node_fs.watch)(contentDir, { recursive: true }, onChange);
|
|
69
84
|
} catch {}
|
|
85
|
+
try {
|
|
86
|
+
(0, node_fs.watch)((0, node_path.join)(crDir, "meta"), { recursive: true }, onChange);
|
|
87
|
+
} catch {}
|
|
70
88
|
try {
|
|
71
89
|
(0, node_fs.watch)(configFile, onChange);
|
|
72
90
|
} catch {}
|
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-CT6gqQfR.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]");
|
|
9
|
+
console.log("Usage: contentrain-query generate [--root <path>] [--watch] [--published] [--at <ISO timestamp>]");
|
|
10
10
|
console.log("");
|
|
11
11
|
console.log("Commands:");
|
|
12
12
|
console.log(" generate Generate typed client from .contentrain/ project files");
|
|
@@ -14,6 +14,8 @@ async function main() {
|
|
|
14
14
|
console.log("Options:");
|
|
15
15
|
console.log(" --root Project root directory (default: cwd)");
|
|
16
16
|
console.log(" --watch Watch for changes and regenerate automatically");
|
|
17
|
+
console.log(" --published Include only published content within its publication window");
|
|
18
|
+
console.log(" --at Reproducible publication timestamp (implies --published)");
|
|
17
19
|
process.exit(0);
|
|
18
20
|
}
|
|
19
21
|
const command = args[0] === "generate" ? "generate" : args[0];
|
|
@@ -26,20 +28,30 @@ async function main() {
|
|
|
26
28
|
const projectRoot = rootArg ? resolve(rootArg) : process.cwd();
|
|
27
29
|
const watchMode = args.includes("--watch");
|
|
28
30
|
try {
|
|
29
|
-
const
|
|
31
|
+
const atIndex = args.indexOf("--at");
|
|
32
|
+
const at = atIndex < 0 ? void 0 : args[atIndex + 1];
|
|
33
|
+
if (atIndex >= 0 && (!at || at.startsWith("--"))) throw new Error("--at requires an ISO timestamp");
|
|
34
|
+
const publication = {
|
|
35
|
+
publishedOnly: args.includes("--published"),
|
|
36
|
+
at
|
|
37
|
+
};
|
|
38
|
+
const result = await generate({
|
|
39
|
+
projectRoot,
|
|
40
|
+
...publication
|
|
41
|
+
});
|
|
30
42
|
console.log(`@contentrain/query — generated client`);
|
|
31
43
|
console.log(` Models: ${result.typesCount}`);
|
|
32
44
|
console.log(` Data modules: ${result.dataModulesCount}`);
|
|
33
45
|
console.log(` Files: ${result.generatedFiles.length}`);
|
|
34
46
|
if (result.packageJsonUpdated) console.log(` package.json: #contentrain imports added`);
|
|
35
47
|
console.log(` Output: .contentrain/client/`);
|
|
36
|
-
if (watchMode) startWatch(projectRoot);
|
|
48
|
+
if (watchMode) startWatch(projectRoot, publication);
|
|
37
49
|
} catch (err) {
|
|
38
50
|
console.error("Generate failed:", err.message);
|
|
39
51
|
process.exit(1);
|
|
40
52
|
}
|
|
41
53
|
}
|
|
42
|
-
function startWatch(projectRoot) {
|
|
54
|
+
function startWatch(projectRoot, publication) {
|
|
43
55
|
const crDir = join(projectRoot, ".contentrain");
|
|
44
56
|
const modelsDir = join(crDir, "models");
|
|
45
57
|
const contentDir = join(crDir, "content");
|
|
@@ -47,7 +59,10 @@ function startWatch(projectRoot) {
|
|
|
47
59
|
let debounceTimer = null;
|
|
48
60
|
const regenerate = async () => {
|
|
49
61
|
try {
|
|
50
|
-
const result = await generate({
|
|
62
|
+
const result = await generate({
|
|
63
|
+
projectRoot,
|
|
64
|
+
...publication
|
|
65
|
+
});
|
|
51
66
|
console.log(` Regenerated: ${result.typesCount} models, ${result.dataModulesCount} data modules`);
|
|
52
67
|
} catch (err) {
|
|
53
68
|
console.error(" Regenerate failed:", err.message);
|
|
@@ -67,6 +82,9 @@ function startWatch(projectRoot) {
|
|
|
67
82
|
try {
|
|
68
83
|
watch(contentDir, { recursive: true }, onChange);
|
|
69
84
|
} catch {}
|
|
85
|
+
try {
|
|
86
|
+
watch(join(crDir, "meta"), { recursive: true }, onChange);
|
|
87
|
+
} catch {}
|
|
70
88
|
try {
|
|
71
89
|
watch(configFile, onChange);
|
|
72
90
|
} catch {}
|
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 { 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]')\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 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 result = await generate({ projectRoot })\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)\n }\n } catch (err) {\n console.error('Generate failed:', (err as Error).message)\n process.exit(1)\n }\n}\n\nfunction startWatch(projectRoot: string) {\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 })\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(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":";;;;;
|
|
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,mGAAmG;AAC/G,UAAQ,IAAI,GAAG;AACf,UAAQ,IAAI,YAAY;AACxB,UAAQ,IAAI,uEAAuE;AACnF,UAAQ,IAAI,GAAG;AACf,UAAQ,IAAI,WAAW;AACvB,UAAQ,IAAI,sDAAsD;AAClE,UAAQ,IAAI,+DAA+D;AAC3E,UAAQ,IAAI,6EAA6E;AACzF,UAAQ,IAAI,yEAAyE;AACrF,UAAQ,KAAK,EAAE;;CAGjB,MAAM,UAAU,KAAK,OAAO,aAAa,aAAa,KAAK;AAE3D,KAAI,YAAY,YAAY;AAC1B,UAAQ,MAAM,oBAAoB,UAAU;AAC5C,UAAQ,KAAK,EAAE;;CAGjB,MAAM,UAAU,KAAK,QAAQ,SAAS;CACtC,MAAM,UAAU,YAAY,KAAK,KAAK,UAAU,KAAK,KAAA;CACrD,MAAM,cAAc,UAAU,QAAQ,QAAQ,GAAG,QAAQ,KAAK;CAC9D,MAAM,YAAY,KAAK,SAAS,UAAU;AAE1C,KAAI;EACF,MAAM,UAAU,KAAK,QAAQ,OAAO;EACpC,MAAM,KAAK,UAAU,IAAI,KAAA,IAAY,KAAK,UAAU;AACpD,MAAI,WAAW,MAAM,CAAC,MAAM,GAAG,WAAW,KAAK,EAAG,OAAM,IAAI,MAAM,iCAAiC;EACnG,MAAM,cAAc;GAAE,eAAe,KAAK,SAAS,cAAc;GAAE;GAAI;EACvE,MAAM,SAAS,MAAM,SAAS;GAAE;GAAa,GAAG;GAAa,CAAC;AAE9D,UAAQ,IAAI,wCAAwC;AACpD,UAAQ,IAAI,mBAAmB,OAAO,aAAa;AACnD,UAAQ,IAAI,mBAAmB,OAAO,mBAAmB;AACzD,UAAQ,IAAI,mBAAmB,OAAO,eAAe,SAAS;AAC9D,MAAI,OAAO,mBACT,SAAQ,IAAI,6CAA6C;AAE3D,UAAQ,IAAI,uCAAuC;AAEnD,MAAI,UACF,YAAW,aAAa,YAAY;UAE/B,KAAK;AACZ,UAAQ,MAAM,oBAAqB,IAAc,QAAQ;AACzD,UAAQ,KAAK,EAAE;;;AAInB,SAAS,WAAW,aAAqB,aAAiC;CACxE,MAAM,QAAQ,KAAK,aAAa,eAAe;CAC/C,MAAM,YAAY,KAAK,OAAO,SAAS;CACvC,MAAM,aAAa,KAAK,OAAO,UAAU;CACzC,MAAM,aAAa,KAAK,OAAO,cAAc;CAE7C,IAAI,gBAAsD;CAE1D,MAAM,aAAa,YAAY;AAC7B,MAAI;GACF,MAAM,SAAS,MAAM,SAAS;IAAE;IAAa,GAAG;IAAa,CAAC;AAC9D,WAAQ,IAAI,kBAAkB,OAAO,WAAW,WAAW,OAAO,iBAAiB,eAAe;WAC3F,KAAK;AACZ,WAAQ,MAAM,wBAAyB,IAAc,QAAQ;;;CAIjE,MAAM,YAAY,QAAgB,aAA4B;AAE5D,MAAI,YAAY,SAAS,WAAW,SAAS,CAAE;AAE/C,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB,iBAAiB;AAC/B,WAAQ,IAAI,sBAAsB,YAAY,YAAY;AAC1D,eAAY;KACX,IAAI;;AAGT,KAAI;AAAE,QAAM,WAAW,EAAE,WAAW,MAAM,EAAE,SAAS;SAAS;AAC9D,KAAI;AAAE,QAAM,YAAY,EAAE,WAAW,MAAM,EAAE,SAAS;SAAS;AAC/D,KAAI;AAAE,QAAM,KAAK,OAAO,OAAO,EAAE,EAAE,WAAW,MAAM,EAAE,SAAS;SAAS;AACxE,KAAI;AAAE,QAAM,YAAY,SAAS;SAAS;AAE1C,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,4CAA4C;AACxD,SAAQ,IAAI,wBAAwB;;AAGtC,MAAM"}
|
|
@@ -1,5 +1,54 @@
|
|
|
1
|
-
let node_path = require("node:path");
|
|
2
1
|
let node_fs_promises = require("node:fs/promises");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
//#region src/generator/publication.ts
|
|
4
|
+
function publicationContext(projectRoot, defaultLocale, options) {
|
|
5
|
+
if (!options.publishedOnly && options.at === void 0) return void 0;
|
|
6
|
+
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");
|
|
7
|
+
const at = options.at === void 0 ? Date.now() : Date.parse(options.at);
|
|
8
|
+
if (!Number.isFinite(at)) throw new Error("Invalid publication --at timestamp");
|
|
9
|
+
return {
|
|
10
|
+
projectRoot,
|
|
11
|
+
defaultLocale,
|
|
12
|
+
at
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Canonical meta is independent of custom content paths and locale strategies. */
|
|
16
|
+
async function publicationMeta(ref, model, context) {
|
|
17
|
+
const locale = model.i18n ? ref.locale ?? context.defaultLocale : context.defaultLocale;
|
|
18
|
+
const parts = [
|
|
19
|
+
context.projectRoot,
|
|
20
|
+
".contentrain",
|
|
21
|
+
"meta",
|
|
22
|
+
model.id
|
|
23
|
+
];
|
|
24
|
+
if (model.kind === "document") parts.push(ref.slug ?? model.id);
|
|
25
|
+
const path = (0, node_path.join)(...parts, `${locale}.json`);
|
|
26
|
+
let text;
|
|
27
|
+
try {
|
|
28
|
+
text = await (0, node_fs_promises.readFile)(path, "utf8");
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error.code === "ENOENT") return void 0;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
const meta = JSON.parse(text);
|
|
34
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) throw new Error(`Invalid publication metadata: ${path}`);
|
|
35
|
+
return meta;
|
|
36
|
+
}
|
|
37
|
+
/** Legacy content without meta remains visible, matching Studio's CDN contract. */
|
|
38
|
+
function isPublishedAt(meta, at) {
|
|
39
|
+
if (meta === void 0) return true;
|
|
40
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return false;
|
|
41
|
+
const value = meta;
|
|
42
|
+
if (value.status && value.status !== "published") return false;
|
|
43
|
+
for (const key of ["publish_at", "expire_at"]) {
|
|
44
|
+
if (value[key] === void 0 || value[key] === null) continue;
|
|
45
|
+
const time = typeof value[key] === "string" ? Date.parse(value[key]) : NaN;
|
|
46
|
+
if (!Number.isFinite(time)) return false;
|
|
47
|
+
if (key === "publish_at" ? time > at : time <= at) return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
3
52
|
//#region src/generator/utils.ts
|
|
4
53
|
async function readJson(filePath) {
|
|
5
54
|
try {
|
|
@@ -243,12 +292,30 @@ function parseValue(raw, forceString = false) {
|
|
|
243
292
|
return raw;
|
|
244
293
|
}
|
|
245
294
|
//#endregion
|
|
295
|
+
Object.defineProperty(exports, "isPublishedAt", {
|
|
296
|
+
enumerable: true,
|
|
297
|
+
get: function() {
|
|
298
|
+
return isPublishedAt;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
246
301
|
Object.defineProperty(exports, "parseFrontmatter", {
|
|
247
302
|
enumerable: true,
|
|
248
303
|
get: function() {
|
|
249
304
|
return parseFrontmatter;
|
|
250
305
|
}
|
|
251
306
|
});
|
|
307
|
+
Object.defineProperty(exports, "publicationContext", {
|
|
308
|
+
enumerable: true,
|
|
309
|
+
get: function() {
|
|
310
|
+
return publicationContext;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
Object.defineProperty(exports, "publicationMeta", {
|
|
314
|
+
enumerable: true,
|
|
315
|
+
get: function() {
|
|
316
|
+
return publicationMeta;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
252
319
|
Object.defineProperty(exports, "readDir", {
|
|
253
320
|
enumerable: true,
|
|
254
321
|
get: function() {
|
|
@@ -1,5 +1,54 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
1
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
//#region src/generator/publication.ts
|
|
4
|
+
function publicationContext(projectRoot, defaultLocale, options) {
|
|
5
|
+
if (!options.publishedOnly && options.at === void 0) return void 0;
|
|
6
|
+
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");
|
|
7
|
+
const at = options.at === void 0 ? Date.now() : Date.parse(options.at);
|
|
8
|
+
if (!Number.isFinite(at)) throw new Error("Invalid publication --at timestamp");
|
|
9
|
+
return {
|
|
10
|
+
projectRoot,
|
|
11
|
+
defaultLocale,
|
|
12
|
+
at
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Canonical meta is independent of custom content paths and locale strategies. */
|
|
16
|
+
async function publicationMeta(ref, model, context) {
|
|
17
|
+
const locale = model.i18n ? ref.locale ?? context.defaultLocale : context.defaultLocale;
|
|
18
|
+
const parts = [
|
|
19
|
+
context.projectRoot,
|
|
20
|
+
".contentrain",
|
|
21
|
+
"meta",
|
|
22
|
+
model.id
|
|
23
|
+
];
|
|
24
|
+
if (model.kind === "document") parts.push(ref.slug ?? model.id);
|
|
25
|
+
const path = join(...parts, `${locale}.json`);
|
|
26
|
+
let text;
|
|
27
|
+
try {
|
|
28
|
+
text = await readFile(path, "utf8");
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error.code === "ENOENT") return void 0;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
const meta = JSON.parse(text);
|
|
34
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) throw new Error(`Invalid publication metadata: ${path}`);
|
|
35
|
+
return meta;
|
|
36
|
+
}
|
|
37
|
+
/** Legacy content without meta remains visible, matching Studio's CDN contract. */
|
|
38
|
+
function isPublishedAt(meta, at) {
|
|
39
|
+
if (meta === void 0) return true;
|
|
40
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return false;
|
|
41
|
+
const value = meta;
|
|
42
|
+
if (value.status && value.status !== "published") return false;
|
|
43
|
+
for (const key of ["publish_at", "expire_at"]) {
|
|
44
|
+
if (value[key] === void 0 || value[key] === null) continue;
|
|
45
|
+
const time = typeof value[key] === "string" ? Date.parse(value[key]) : NaN;
|
|
46
|
+
if (!Number.isFinite(time)) return false;
|
|
47
|
+
if (key === "publish_at" ? time > at : time <= at) return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
3
52
|
//#region src/generator/utils.ts
|
|
4
53
|
async function readJson(filePath) {
|
|
5
54
|
try {
|
|
@@ -243,6 +292,6 @@ function parseValue(raw, forceString = false) {
|
|
|
243
292
|
return raw;
|
|
244
293
|
}
|
|
245
294
|
//#endregion
|
|
246
|
-
export { readJson as a, readDir as i, stringLikeFieldKeys as n, readText as o, readProjectManifest as r, writeText as s, parseFrontmatter as t };
|
|
295
|
+
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 };
|
|
247
296
|
|
|
248
|
-
//# sourceMappingURL=frontmatter-
|
|
297
|
+
//# sourceMappingURL=frontmatter-D-QXY_WH.mjs.map
|