@micacms/astro 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Müdespacher
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # @micacms/astro
2
+
3
+ > **Experimental.** Pre-1.0: the API and the content format can still change
4
+ > without notice. Use it at your own risk.
5
+
6
+ Reads a Mica content repository as Astro Content Collections, through the
7
+ Content Layer API — and publishes the content model for Mica to read back.
8
+
9
+ The build needs no CMS running, no URL and no token. Content is files in the
10
+ checkout the build is already in, so the website cannot be broken by the CMS
11
+ being down, slow, or mid-deploy.
12
+
13
+ ```ts
14
+ // src/mica.ts
15
+ import { collection, defineSite, slug, text } from "@micacms/model";
16
+
17
+ export const site = defineSite({
18
+ root: "./content",
19
+ collections: {
20
+ news: collection({
21
+ label: "News",
22
+ fields: { title: text({ required: true }), slug: slug({ from: "title" }) },
23
+ }),
24
+ },
25
+ });
26
+ ```
27
+
28
+ ```ts
29
+ // src/content.config.ts
30
+ import { micaCollections } from "@micacms/astro";
31
+
32
+ import { site } from "./mica";
33
+
34
+ export const collections = micaCollections(site);
35
+ ```
36
+
37
+ That is the whole setup. **Nothing goes in `astro.config`** — which is also what
38
+ lets `content.config.ts` import `astro:content`, a virtual module that only
39
+ exists inside the app's own module graph.
40
+
41
+ Pages then use ordinary Astro:
42
+
43
+ ```astro
44
+ ---
45
+ import { getCollection } from "astro:content";
46
+ const news = await getCollection("news");
47
+ ---
48
+ ```
49
+
50
+ `getCollection("news")` is fully typed from the model, with no code generation
51
+ step — so renaming a field breaks the build rather than the page.
52
+
53
+ ## What `micaCollections` does
54
+
55
+ One loader and one Zod schema per collection and singleton. Each document
56
+ carries a `_mica` field with what the model knows about it; a singleton becomes
57
+ a one-entry collection, reachable with the same `getEntry` as everything else.
58
+
59
+ The loaders also do the two things that used to need an integration, because
60
+ Astro hands a loader exactly what they require:
61
+
62
+ - **Publish the model** to `<root>/.mica/schema.json`, once per build or
63
+ dev-server start. Commit that file: it is how a Mica deployment learns what
64
+ this project defines. `publishModel: false` turns it off, for a project that
65
+ is not edited through Mica.
66
+ - **Reload in dev.** Astro's watcher follows the module graph and content files
67
+ are not in it, because no page imports `projects/haus-a/index.json`. Each
68
+ loader watches its own directory and coalesces the burst of writes that saving
69
+ one entry produces (`debounceMs`, 100 ms by default).
70
+
71
+ ## Mixing with your own collections
72
+
73
+ The return value is a plain object:
74
+
75
+ ```ts
76
+ export const collections = {
77
+ ...micaCollections(site),
78
+ docs: defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/docs" }) }),
79
+ };
80
+ ```
81
+
82
+ Mica edits what is in the model and does not know the rest exists. A save in one
83
+ of its collections reloads that collection only — never a foreign loader.
84
+
85
+ ## Rich text
86
+
87
+ `renderRichText(source)` turns a Markdoc field into an HTML string;
88
+ `transformRichText(source)` returns the renderable tree instead, for a project
89
+ that wants to render with its own components.
90
+
91
+ ## Install
92
+
93
+ ```bash
94
+ npm install @micacms/astro
95
+ ```
96
+
97
+ Astro 5 is a peer dependency. Requires Node 22.5 or newer. Licence: MIT.
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @micacms/astro — a Git repository as an Astro content source.
3
+ *
4
+ * // src/content.config.ts
5
+ * import { micaCollections } from "@micacms/astro";
6
+ * import { defineSite } from "@micacms/model";
7
+ *
8
+ * export const site = defineSite({ collections: { … } });
9
+ * export const collections = micaCollections(site, { root: "./content" });
10
+ *
11
+ * From there the website uses ordinary Astro APIs — `getCollection("projects")`
12
+ * — and knows nothing about how the content arrived.
13
+ *
14
+ * There is no CMS in this path. The loaders read files that are already in the
15
+ * working tree, so a build needs a checkout and nothing else: no running
16
+ * server, no token, no network. That is the point of the Git-native design.
17
+ * Publishing is a commit, and CI builds the commit.
18
+ */
19
+ import { type ContentSource, type InferCollection, type InferSingleton, type Site } from "@micacms/model";
20
+ import { z } from "zod";
21
+ import { DEFAULT_CONTENT_ROOT, resolveContentRoot } from "./root.js";
22
+ export interface MicaCollectionsOptions {
23
+ /**
24
+ * Overrides `defineSite({ root })`, which is where the content root belongs.
25
+ *
26
+ * Relative paths resolve against the Astro project root, like Astro's own
27
+ * `glob({ base })`. An absolute path is used as given — which is what a
28
+ * project whose content is a separate repository, checked out by CI
29
+ * somewhere else, wants.
30
+ */
31
+ root?: string;
32
+ /** Overrides `root` entirely. Used by tests, and by a non-filesystem source. */
33
+ source?: ContentSource;
34
+ /**
35
+ * Turns off publishing the model to `<root>/.mica/schema.json`.
36
+ *
37
+ * Only for a project that is not edited through Mica at all: without that
38
+ * file in the repository, Mica has no model and refuses to start.
39
+ */
40
+ publishModel?: false;
41
+ /**
42
+ * How long to wait after a content change before reloading, in dev.
43
+ *
44
+ * Saving one entry touches several files — `index.json` and every `.mdoc`
45
+ * beside it — and a Git checkout touches all of them at once. One reload
46
+ * after the burst is both faster and less confusing than one per file.
47
+ */
48
+ debounceMs?: number;
49
+ }
50
+ /**
51
+ * Entry metadata attached to every collection document.
52
+ *
53
+ * Much smaller than it was when a database backed the CMS. Timestamps and
54
+ * version numbers came from table columns that no longer exist; in Git the
55
+ * equivalents are commit metadata, which a static build has no cheap way to
56
+ * read and no strong reason to need. A date an editor cares about belongs in
57
+ * the model as a `date()` field, where it is editorial rather than incidental.
58
+ *
59
+ * Namespaced because these keys share a namespace with the customer's own
60
+ * fields, and `position` is a plausible field name.
61
+ */
62
+ export interface MicaMeta {
63
+ /** The entry slug, which is its identity in the repository. */
64
+ id: string;
65
+ slug: string;
66
+ collection: string;
67
+ /** Index in the collection's order, manual when `_order.json` says so. */
68
+ position: number;
69
+ }
70
+ /** What a document looks like inside Astro: the payload plus `_mica`. */
71
+ export type AstroDocument<T> = T & {
72
+ _mica: MicaMeta;
73
+ };
74
+ /**
75
+ * The shape Astro's config loader expects.
76
+ *
77
+ * Deliberately not built with `defineCollection` from `astro:content`: that is a
78
+ * virtual module, resolvable only inside an Astro app's module graph, and this
79
+ * package is pre-compiled. `defineCollection` does nothing more than stamp this
80
+ * `type`, so producing the object directly keeps the package importable
81
+ * anywhere — including from a plain unit test.
82
+ */
83
+ export interface AstroCollectionConfig<T = unknown> {
84
+ type: "content_layer";
85
+ loader: ContentLoader;
86
+ /**
87
+ * Typed as `ZodType<T>` rather than a bare `ZodObject`, because Astro derives
88
+ * `CollectionEntry<"projects">["data"]` from `z.infer` of whatever this is.
89
+ * Left generic, every field in every template would degrade to `any` — and
90
+ * the point of the shared model is that renaming a field breaks the build.
91
+ */
92
+ schema: z.ZodType<T>;
93
+ }
94
+ /** The subset of Astro's `Loader` contract this package uses. */
95
+ export interface ContentLoader {
96
+ name: string;
97
+ load(context: LoaderContext): Promise<void>;
98
+ }
99
+ export interface LoaderContext {
100
+ store: {
101
+ clear(): void;
102
+ set(entry: {
103
+ id: string;
104
+ data: Record<string, unknown>;
105
+ digest?: string;
106
+ }): boolean;
107
+ keys(): string[];
108
+ };
109
+ logger: {
110
+ info(message: string): void;
111
+ warn(message: string): void;
112
+ error(message: string): void;
113
+ debug(message: string): void;
114
+ };
115
+ meta: {
116
+ get(key: string): string | undefined;
117
+ set(key: string, value: string): void;
118
+ };
119
+ /**
120
+ * Astro's resolved config. Optional because this interface is a hand-written
121
+ * subset and the tests build a context by hand; `config.root` is what turns a
122
+ * relative content root into a path.
123
+ */
124
+ config?: {
125
+ root: URL;
126
+ };
127
+ /**
128
+ * Present only while `astro dev` runs. Astro's watcher follows the module
129
+ * graph, and content files are not in it — no page imports
130
+ * `projects/haus-a/index.json` — so each loader watches its own directory
131
+ * here, exactly as Astro's own `glob` loader does.
132
+ */
133
+ watcher?: {
134
+ add(path: string): void;
135
+ on(event: "add" | "change" | "unlink", handler: (path: string) => void): void;
136
+ };
137
+ parseData(input: {
138
+ id: string;
139
+ data: Record<string, unknown>;
140
+ }): Promise<Record<string, unknown>>;
141
+ generateDigest(data: Record<string, unknown> | string): string;
142
+ }
143
+ export type MicaCollections<S extends Site> = {
144
+ [K in keyof S["collections"]]: AstroCollectionConfig<AstroDocument<InferCollection<S["collections"][K]>>>;
145
+ } & {
146
+ [K in keyof S["singletons"]]: AstroCollectionConfig<InferSingleton<S["singletons"][K]>>;
147
+ };
148
+ export declare function micaCollections<S extends Site>(site: S, options?: MicaCollectionsOptions): MicaCollections<S>;
149
+ /** The document type for one collection, as it appears in Astro. */
150
+ export type CollectionDocument<S extends Site, K extends keyof S["collections"]> = AstroDocument<InferCollection<S["collections"][K]>>;
151
+ /** The document type for one singleton. */
152
+ export type SingletonDocument<S extends Site, K extends keyof S["singletons"]> = InferSingleton<S["singletons"][K]>;
153
+ export { manifestFor } from "./manifest.js";
154
+ export { DEFAULT_CONTENT_ROOT, resolveContentRoot };
155
+ export { renderRichText, transformRichText, validateRichText, type RichTextOptions, } from "./richtext.js";
156
+ /**
157
+ * Re-exported for convenience, so a project that only depends on this package
158
+ * can still build a source or read a collection. They are defined in
159
+ * `@micacms/model`, where the Mica server can reach them too.
160
+ */
161
+ export { ContentReadError, ContentSourceError, ORDER_FILE, memorySource, readCollection, readSingleton, type ContentSource, type DirectoryEntry, } from "@micacms/model";
162
+ export { fileSystemSource } from "@micacms/model/node";
163
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @micacms/astro — a Git repository as an Astro content source.
3
+ *
4
+ * // src/content.config.ts
5
+ * import { micaCollections } from "@micacms/astro";
6
+ * import { defineSite } from "@micacms/model";
7
+ *
8
+ * export const site = defineSite({ collections: { … } });
9
+ * export const collections = micaCollections(site, { root: "./content" });
10
+ *
11
+ * From there the website uses ordinary Astro APIs — `getCollection("projects")`
12
+ * — and knows nothing about how the content arrived.
13
+ *
14
+ * There is no CMS in this path. The loaders read files that are already in the
15
+ * working tree, so a build needs a checkout and nothing else: no running
16
+ * server, no token, no network. That is the point of the Git-native design.
17
+ * Publishing is a commit, and CI builds the commit.
18
+ */
19
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
20
+ import { dirname, join, sep } from "node:path";
21
+ import { fieldsSchema, MANIFEST_PATH, readCollection, readSingleton, } from "@micacms/model";
22
+ import { fileSystemSource } from "@micacms/model/node";
23
+ import { z } from "zod";
24
+ import { manifestFor } from "./manifest.js";
25
+ import { DEFAULT_CONTENT_ROOT, resolveContentRoot } from "./root.js";
26
+ const metaSchema = z.object({
27
+ id: z.string(),
28
+ slug: z.string(),
29
+ collection: z.string(),
30
+ position: z.number(),
31
+ });
32
+ export function micaCollections(site, options = {}) {
33
+ /**
34
+ * Everything below is resolved on first load rather than here, because the
35
+ * Astro project root is something only a loader is told — and it is what
36
+ * turns `./content` into a directory. Memoised: one root and one source,
37
+ * however many collections and however many reloads.
38
+ */
39
+ let root;
40
+ let source;
41
+ const rootFor = (context) => (root ??= resolveContentRoot(options.root ?? site.root, context.config?.root));
42
+ const sourceFor = (context) => (source ??= options.source ?? fileSystemSource(rootFor(context)));
43
+ /** Published once per process; a model change restarts the dev server. */
44
+ let publishing;
45
+ const publish = (context) => (publishing ??=
46
+ options.publishModel === false || options.source
47
+ ? Promise.resolve()
48
+ : publishModel(site, rootFor(context), context.logger));
49
+ /** Collections whose watcher is already registered, so a reload adds none. */
50
+ const watching = new Set();
51
+ const watch = (context, name, reload) => {
52
+ const watcher = context.watcher;
53
+ if (!watcher || options.source || watching.has(name))
54
+ return;
55
+ watching.add(name);
56
+ const base = rootFor(context);
57
+ watcher.add(base);
58
+ // A collection lives in `<root>/<name>/`; a singleton is either
59
+ // `<root>/<name>.json` or `<root>/<name>/…`. Both are covered by this
60
+ // prefix, and nothing outside it can concern this loader — which is what
61
+ // keeps one collection's save from re-reading every other one.
62
+ const prefix = join(base, name);
63
+ const mine = (path) => path === `${prefix}.json` || path.startsWith(prefix + sep);
64
+ let timer;
65
+ const onChange = (path) => {
66
+ // The manifest is this package's own output; reloading content because
67
+ // the model was written is a loop waiting to happen.
68
+ if (path.endsWith(MANIFEST_PATH) || !mine(path))
69
+ return;
70
+ if (timer)
71
+ clearTimeout(timer);
72
+ timer = setTimeout(() => {
73
+ // Content that fails its own schema lands here. The dev server has to
74
+ // survive it: the next save is very likely the fix.
75
+ void reload().catch((error) => {
76
+ context.logger.error(`Could not reload content: ${describe(error)}`);
77
+ });
78
+ }, options.debounceMs ?? 100);
79
+ // A pending reload is never a reason to keep the process alive.
80
+ timer.unref?.();
81
+ };
82
+ watcher.on("change", onChange);
83
+ watcher.on("add", onChange);
84
+ watcher.on("unlink", onChange);
85
+ };
86
+ const collections = {};
87
+ for (const [name, def] of Object.entries(site.collections)) {
88
+ collections[name] = {
89
+ type: "content_layer",
90
+ // `storage` rather than `output`: a media reference is a path into the
91
+ // repository at rest and a path into the repository once loaded. There
92
+ // is no expansion step left to tell the two apart.
93
+ schema: fieldsSchema(def.fields, "storage").extend({ _mica: metaSchema }),
94
+ loader: {
95
+ name: `mica:${name}`,
96
+ load: async (context) => {
97
+ const read = async () => {
98
+ const source = sourceFor(context);
99
+ const entries = await guard(context, source, () => readCollection(source, def, name));
100
+ // A full replace, not a merge: an entry deleted from the repository
101
+ // has to disappear from the build.
102
+ context.store.clear();
103
+ for (const entry of entries) {
104
+ const meta = {
105
+ id: entry.slug,
106
+ slug: entry.slug,
107
+ collection: name,
108
+ position: entry.position,
109
+ };
110
+ const data = await context.parseData({
111
+ id: entry.slug,
112
+ data: { ...entry.data, _mica: meta },
113
+ });
114
+ context.store.set({ id: entry.slug, data, digest: context.generateDigest(data) });
115
+ }
116
+ context.logger.info(`Loaded ${entries.length} ${def.label.toLowerCase()}`);
117
+ };
118
+ await publish(context);
119
+ await read();
120
+ watch(context, name, read);
121
+ },
122
+ },
123
+ };
124
+ }
125
+ // A singleton becomes a one-entry collection whose id is its name, so the
126
+ // website reaches it with the same `getEntry` it uses for everything else.
127
+ for (const [name, def] of Object.entries(site.singletons)) {
128
+ collections[name] = {
129
+ type: "content_layer",
130
+ schema: fieldsSchema(def.fields, "storage"),
131
+ loader: {
132
+ name: `mica:${name}`,
133
+ load: async (context) => {
134
+ const read = async () => {
135
+ const source = sourceFor(context);
136
+ const data = await guard(context, source, () => readSingleton(source, def, name));
137
+ context.store.clear();
138
+ if (data === null) {
139
+ // Not fatal. A site is normally deployed before every settings
140
+ // form has been filled in, and the page that reads this singleton
141
+ // reports the absence far better than the loader can.
142
+ context.logger.warn(`No content for the "${def.label}" settings yet.`);
143
+ return;
144
+ }
145
+ const parsed = await context.parseData({ id: name, data });
146
+ context.store.set({ id: name, data: parsed, digest: context.generateDigest(parsed) });
147
+ };
148
+ await publish(context);
149
+ await read();
150
+ watch(context, name, read);
151
+ },
152
+ },
153
+ };
154
+ }
155
+ return collections;
156
+ }
157
+ /**
158
+ * Writes the model into the content root, for Mica to read back.
159
+ *
160
+ * Mica holds no content model: the Astro project is the only place a collection
161
+ * is defined, and this is how it says so. The file goes *inside the content
162
+ * root* so that it travels to Mica the way content does — through Git and the
163
+ * repository connector — with nothing to mount and no second channel to keep in
164
+ * step. Commit it.
165
+ *
166
+ * Done from a loader rather than an integration because this is the moment both
167
+ * facts are in hand: the model, and the project root that turns `./content`
168
+ * into a directory. It also means `astro.config` needs no Mica in it, which is
169
+ * what lets a project define Mica collections and ordinary Astro ones in the
170
+ * same `content.config.ts`.
171
+ */
172
+ async function publishModel(site, root, logger) {
173
+ const target = join(root, MANIFEST_PATH);
174
+ const contents = `${JSON.stringify(manifestFor(site), null, 2)}\n`;
175
+ try {
176
+ // Only when it differs. The content root is watched in dev, and rewriting
177
+ // an identical file on every start would ask for a reload for no reason.
178
+ if ((await readFile(target, "utf8").catch(() => null)) === contents)
179
+ return;
180
+ await mkdir(dirname(target), { recursive: true });
181
+ await writeFile(target, contents, "utf8");
182
+ logger.info(`Wrote the content model to ${MANIFEST_PATH} — commit it for Mica to read.`);
183
+ }
184
+ catch (error) {
185
+ // A site that builds correctly should not fail over this, and a read-only
186
+ // checkout is a normal way to build one in CI. Mica reports a missing model
187
+ // itself, and far more usefully.
188
+ logger.warn(`Could not write the content model: ${describe(error)}`);
189
+ }
190
+ }
191
+ function describe(error) {
192
+ return error instanceof Error ? error.message : String(error);
193
+ }
194
+ /**
195
+ * Reports a read failure against the content root that produced it.
196
+ *
197
+ * A build failing on unreadable content is correct — publishing a half-empty
198
+ * site would be worse — but the message has to name the directory that was
199
+ * read, because the usual cause is a content root pointing at the wrong place.
200
+ */
201
+ async function guard(context, source, read) {
202
+ try {
203
+ return await read();
204
+ }
205
+ catch (error) {
206
+ const detail = error instanceof Error ? error.message : String(error);
207
+ context.logger.error(detail);
208
+ throw new Error(`Could not read content from ${source.origin}.\n ${detail}`, { cause: error });
209
+ }
210
+ }
211
+ export { manifestFor } from "./manifest.js";
212
+ export { DEFAULT_CONTENT_ROOT, resolveContentRoot };
213
+ export { renderRichText, transformRichText, validateRichText, } from "./richtext.js";
214
+ /**
215
+ * Re-exported for convenience, so a project that only depends on this package
216
+ * can still build a source or read a collection. They are defined in
217
+ * `@micacms/model`, where the Mica server can reach them too.
218
+ */
219
+ export { ContentReadError, ContentSourceError, ORDER_FILE, memorySource, readCollection, readSingleton, } from "@micacms/model";
220
+ export { fileSystemSource } from "@micacms/model/node";
221
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The protocol manifest this project publishes.
3
+ *
4
+ * Its own module rather than a function in `index.ts`, so that the integration
5
+ * can emit a manifest without importing the loaders, and the loaders can be
6
+ * imported without pulling in the integration. The two files would otherwise
7
+ * import each other.
8
+ */
9
+ import { type ProtocolManifest, type Site } from "@micacms/model";
10
+ /**
11
+ * Mica holds no model of its own. The Astro project is the only place a
12
+ * collection is defined, and this is how it says so.
13
+ */
14
+ export declare function manifestFor(site: Site): ProtocolManifest;
15
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAkB,KAAK,gBAAgB,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAKlF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,gBAAgB,CAExD"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The protocol manifest this project publishes.
3
+ *
4
+ * Its own module rather than a function in `index.ts`, so that the integration
5
+ * can emit a manifest without importing the loaders, and the loaders can be
6
+ * imported without pulling in the integration. The two files would otherwise
7
+ * import each other.
8
+ */
9
+ import { createManifest } from "@micacms/model";
10
+ /** Replaced at publish time; the fallback keeps a workspace build honest. */
11
+ const PACKAGE_VERSION = "0.0.1";
12
+ /**
13
+ * Mica holds no model of its own. The Astro project is the only place a
14
+ * collection is defined, and this is how it says so.
15
+ */
16
+ export function manifestFor(site) {
17
+ return createManifest(site, `@micacms/astro@${PACKAGE_VERSION}`);
18
+ }
19
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAoC,MAAM,gBAAgB,CAAC;AAElF,6EAA6E;AAC7E,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAU;IACpC,OAAO,cAAc,CAAC,IAAI,EAAE,kBAAkB,eAAe,EAAE,CAAC,CAAC;AACnE,CAAC"}
package/dist/read.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Entry discovery.
3
+ *
4
+ * Turns a content directory into payloads, using the layout rules the model
5
+ * defines. Nothing here decides where a file belongs — `@micacms/model` does —
6
+ * so a directory written by the editor and a directory written by hand are read
7
+ * the same way.
8
+ */
9
+ import { type Collection, type Singleton } from "@micacms/model";
10
+ import type { ContentSource } from "./source.js";
11
+ /**
12
+ * Optional manual ordering for a collection, listing slugs in the order an
13
+ * editor arranged them.
14
+ *
15
+ * A separate file rather than a field on each entry, because order is a
16
+ * property of the collection: writing it into the entries would make a single
17
+ * drag rewrite every one of them, and the resulting Git diff would say nothing
18
+ * about what changed.
19
+ */
20
+ export declare const ORDER_FILE = "_order.json";
21
+ export interface ContentEntry {
22
+ slug: string;
23
+ /** Index in the collection's order — manual when `_order.json` says so. */
24
+ position: number;
25
+ data: Record<string, unknown>;
26
+ }
27
+ /** Raised with the offending path attached, which a bare parse error lacks. */
28
+ export declare class ContentReadError extends Error {
29
+ readonly path: string;
30
+ name: string;
31
+ constructor(message: string, path: string, options?: {
32
+ cause?: unknown;
33
+ });
34
+ }
35
+ export declare function readCollection(source: ContentSource, def: Collection, name: string): Promise<ContentEntry[]>;
36
+ /**
37
+ * A singleton's payload, or null when the repository has no file for it yet.
38
+ *
39
+ * Null rather than an error: a site is normally deployed before every settings
40
+ * form has been filled in, and the collection schema is a better place to
41
+ * report a missing required value than the reader is.
42
+ */
43
+ export declare function readSingleton(source: ContentSource, def: Singleton, name: string): Promise<Record<string, unknown> | null>;
44
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../src/read.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAOL,KAAK,UAAU,EAEf,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,gBAAgB,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,+EAA+E;AAC/E,qBAAa,gBAAiB,SAAQ,KAAK;IAIvC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAHd,IAAI,SAAsB;gBAEjC,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAIhC;AAMD,wBAAsB,cAAc,CAClC,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,EAAE,CAAC,CAYzB;AA4ED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAEzC"}
package/dist/read.js ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Entry discovery.
3
+ *
4
+ * Turns a content directory into payloads, using the layout rules the model
5
+ * defines. Nothing here decides where a file belongs — `@micacms/model` does —
6
+ * so a directory written by the editor and a directory written by hand are read
7
+ * the same way.
8
+ */
9
+ import { DOCUMENT_EXTENSION, FormatError, INDEX_FILE, entryLayout, isValidSlug, parseEntry, } from "@micacms/model";
10
+ /**
11
+ * Optional manual ordering for a collection, listing slugs in the order an
12
+ * editor arranged them.
13
+ *
14
+ * A separate file rather than a field on each entry, because order is a
15
+ * property of the collection: writing it into the entries would make a single
16
+ * drag rewrite every one of them, and the resulting Git diff would say nothing
17
+ * about what changed.
18
+ */
19
+ export const ORDER_FILE = "_order.json";
20
+ /** Raised with the offending path attached, which a bare parse error lacks. */
21
+ export class ContentReadError extends Error {
22
+ path;
23
+ name = "ContentReadError";
24
+ constructor(message, path, options) {
25
+ super(message, options);
26
+ this.path = path;
27
+ }
28
+ }
29
+ /* ------------------------------------------------------------------ *
30
+ * Collections
31
+ * ------------------------------------------------------------------ */
32
+ export async function readCollection(source, def, name) {
33
+ const slugs = await listSlugs(source, def.fields, name);
34
+ const ordered = await applyOrder(source, name, slugs);
35
+ const entries = [];
36
+ for (const [position, slug] of ordered.entries()) {
37
+ const data = await readEntry(source, def.fields, `${name}/${slug}`, entryBase(def.fields, name, slug));
38
+ if (data)
39
+ entries.push({ slug, position, data });
40
+ }
41
+ return entries;
42
+ }
43
+ /**
44
+ * Slugs present in a collection directory.
45
+ *
46
+ * The layout decides what to look at: directory-shaped entries are directories,
47
+ * file-shaped ones are `.json` files. Anything that does not match — a stray
48
+ * README, the order file, a directory without an `index.json` — is skipped
49
+ * rather than rejected, because a content repository belongs to its owner and
50
+ * may hold things Mica did not put there.
51
+ */
52
+ async function listSlugs(source, fields, name) {
53
+ const directoryLayout = entryLayout(fields) === "directory";
54
+ const items = await source.list(name);
55
+ const slugs = [];
56
+ for (const item of items) {
57
+ if (directoryLayout) {
58
+ if (item.type !== "directory")
59
+ continue;
60
+ if (isValidSlug(item.name))
61
+ slugs.push(item.name);
62
+ continue;
63
+ }
64
+ if (item.type !== "file" || !item.name.endsWith(".json"))
65
+ continue;
66
+ const slug = item.name.slice(0, -".json".length);
67
+ if (isValidSlug(slug))
68
+ slugs.push(slug);
69
+ }
70
+ return slugs;
71
+ }
72
+ /**
73
+ * Reorders slugs to match `_order.json`.
74
+ *
75
+ * Tolerant in both directions: a slug the file does not mention goes after the
76
+ * ones it does, and a slug the file mentions but which no longer exists is
77
+ * dropped. Either happens routinely when someone adds or deletes an entry in
78
+ * Git without touching the order file.
79
+ */
80
+ async function applyOrder(source, name, slugs) {
81
+ const path = `${name}/${ORDER_FILE}`;
82
+ const raw = await source.read(path);
83
+ if (raw === null)
84
+ return slugs;
85
+ let listed;
86
+ try {
87
+ listed = JSON.parse(raw);
88
+ }
89
+ catch (error) {
90
+ throw new ContentReadError(`${path} is not valid JSON.`, path, { cause: error });
91
+ }
92
+ if (!Array.isArray(listed)) {
93
+ throw new ContentReadError(`${path} must be an array of slugs.`, path);
94
+ }
95
+ const available = new Set(slugs);
96
+ const ordered = listed.filter((slug) => typeof slug === "string" && available.delete(slug));
97
+ // Whatever the order file did not mention keeps its alphabetical position.
98
+ return [...ordered, ...slugs.filter((slug) => available.has(slug))];
99
+ }
100
+ /* ------------------------------------------------------------------ *
101
+ * Singletons
102
+ * ------------------------------------------------------------------ */
103
+ /**
104
+ * A singleton's payload, or null when the repository has no file for it yet.
105
+ *
106
+ * Null rather than an error: a site is normally deployed before every settings
107
+ * form has been filled in, and the collection schema is a better place to
108
+ * report a missing required value than the reader is.
109
+ */
110
+ export async function readSingleton(source, def, name) {
111
+ return readEntry(source, def.fields, name, entryBase(def.fields, name));
112
+ }
113
+ /* ------------------------------------------------------------------ *
114
+ * Shared
115
+ * ------------------------------------------------------------------ */
116
+ /** The directory an entry's files live in, or null for file-shaped entries. */
117
+ function entryBase(fields, ...segments) {
118
+ return entryLayout(fields) === "directory" ? segments.join("/") : null;
119
+ }
120
+ async function readEntry(source, fields, label, base) {
121
+ const dataPath = base === null ? `${label}.json` : `${base}/${INDEX_FILE}`;
122
+ const data = await source.read(dataPath);
123
+ if (data === null)
124
+ return null;
125
+ const documents = base === null ? {} : await readDocuments(source, base);
126
+ let parsed;
127
+ try {
128
+ parsed = parseEntry(fields, { data, documents });
129
+ }
130
+ catch (error) {
131
+ // A `FormatError` names the problem but not the file, and a build that
132
+ // says only "Content is not valid JSON" is close to useless.
133
+ if (error instanceof FormatError) {
134
+ throw new ContentReadError(`${dataPath}: ${error.message}`, dataPath, { cause: error });
135
+ }
136
+ throw error;
137
+ }
138
+ return base === null ? parsed : resolveMedia(fields, parsed, base);
139
+ }
140
+ /**
141
+ * Rewrites media references to paths relative to the content root.
142
+ *
143
+ * On disk a reference is written the way a person reading the entry would
144
+ * write it: `media/exterior.jpg` for a file colocated with the entry, or
145
+ * `/shared/logo.svg` for one the whole site uses. Both are ambiguous once the
146
+ * entry is out of its directory, so the root-relative form is what leaves here.
147
+ */
148
+ function resolveMedia(fields, data, base) {
149
+ const resolveOne = (value) => typeof value === "string" && value !== ""
150
+ ? value.startsWith("/")
151
+ ? value.slice(1)
152
+ : `${base}/${value}`
153
+ : value;
154
+ const visit = (map, record) => {
155
+ const result = { ...record };
156
+ for (const [key, field] of Object.entries(map)) {
157
+ const value = record[key];
158
+ if (field.kind === "media") {
159
+ result[key] = Array.isArray(value) ? value.map(resolveOne) : resolveOne(value);
160
+ }
161
+ else if (field.kind === "object" && isRecord(value)) {
162
+ result[key] = visit(field.fields, value);
163
+ }
164
+ else if (field.kind === "repeater" && Array.isArray(value)) {
165
+ result[key] = value.map((row) => (isRecord(row) ? visit(field.fields, row) : row));
166
+ }
167
+ }
168
+ return result;
169
+ };
170
+ return visit(fields, data);
171
+ }
172
+ function isRecord(value) {
173
+ return value !== null && typeof value === "object" && !Array.isArray(value);
174
+ }
175
+ async function readDocuments(source, base) {
176
+ const documents = {};
177
+ for (const item of await source.list(base)) {
178
+ if (item.type !== "file" || !item.name.endsWith(DOCUMENT_EXTENSION))
179
+ continue;
180
+ const contents = await source.read(`${base}/${item.name}`);
181
+ if (contents !== null)
182
+ documents[item.name] = contents;
183
+ }
184
+ return documents;
185
+ }
186
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../src/read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Rendering a rich-text field.
3
+ *
4
+ * A `richText()` field is persisted as Markdoc, and this is what turns it into
5
+ * HTML at build time. It runs during `astro build` only; the deployed page is
6
+ * static, and no visitor's browser ever sees Markdoc.
7
+ *
8
+ * Markdoc rather than Markdown-with-HTML for one reason that matters: a Markdoc
9
+ * document can only produce what the configuration allows. An undeclared
10
+ * `{% tag %}` renders as nothing rather than as itself, and raw HTML is inert
11
+ * unless it is explicitly permitted — which it is not here. So the canonical
12
+ * content format cannot carry markup that escapes the site's own styling, and a
13
+ * compromised or careless editor session cannot inject script into a build.
14
+ */
15
+ import Markdoc, { type Config, type RenderableTreeNode } from "@markdoc/markdoc";
16
+ export interface RichTextOptions {
17
+ /**
18
+ * Extra Markdoc configuration: custom tags, nodes or variables.
19
+ *
20
+ * The place a project adds a callout or a gallery block without giving up the
21
+ * closed set that makes the format safe. A tag must be declared here to
22
+ * render at all.
23
+ */
24
+ config?: Config;
25
+ }
26
+ /**
27
+ * Markdoc source to an HTML string.
28
+ *
29
+ * Returns an empty string for empty input, so a caller can render the result
30
+ * directly without a null check ahead of it.
31
+ */
32
+ export declare function renderRichText(source: string | null | undefined, options?: RichTextOptions): string;
33
+ /**
34
+ * The intermediate renderable tree, for a project that wants to render with
35
+ * its own components rather than to an HTML string.
36
+ */
37
+ export declare function transformRichText(source: string, options?: RichTextOptions): RenderableTreeNode;
38
+ /**
39
+ * Problems Markdoc found in a document, for a build that wants to be strict.
40
+ *
41
+ * Not called by `renderRichText`: a malformed heading should not stop a site
42
+ * from deploying, and Markdoc degrades to plain text rather than to nothing.
43
+ * Exposed so a project can decide otherwise.
44
+ */
45
+ export declare function validateRichText(source: string, options?: RichTextOptions): Markdoc.ValidateError[];
46
+ //# sourceMappingURL=richtext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"richtext.d.ts","sourceRoot":"","sources":["../src/richtext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,OAAO,EAAE,EAAE,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEjF,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAKvG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,eAAoB,GAC5B,kBAAkB,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,2BAE7E"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Rendering a rich-text field.
3
+ *
4
+ * A `richText()` field is persisted as Markdoc, and this is what turns it into
5
+ * HTML at build time. It runs during `astro build` only; the deployed page is
6
+ * static, and no visitor's browser ever sees Markdoc.
7
+ *
8
+ * Markdoc rather than Markdown-with-HTML for one reason that matters: a Markdoc
9
+ * document can only produce what the configuration allows. An undeclared
10
+ * `{% tag %}` renders as nothing rather than as itself, and raw HTML is inert
11
+ * unless it is explicitly permitted — which it is not here. So the canonical
12
+ * content format cannot carry markup that escapes the site's own styling, and a
13
+ * compromised or careless editor session cannot inject script into a build.
14
+ */
15
+ import Markdoc, {} from "@markdoc/markdoc";
16
+ /**
17
+ * Markdoc source to an HTML string.
18
+ *
19
+ * Returns an empty string for empty input, so a caller can render the result
20
+ * directly without a null check ahead of it.
21
+ */
22
+ export function renderRichText(source, options = {}) {
23
+ if (!source || source.trim() === "")
24
+ return "";
25
+ const tree = transformRichText(source, options);
26
+ return Markdoc.renderers.html(tree);
27
+ }
28
+ /**
29
+ * The intermediate renderable tree, for a project that wants to render with
30
+ * its own components rather than to an HTML string.
31
+ */
32
+ export function transformRichText(source, options = {}) {
33
+ return Markdoc.transform(Markdoc.parse(source), options.config ?? {});
34
+ }
35
+ /**
36
+ * Problems Markdoc found in a document, for a build that wants to be strict.
37
+ *
38
+ * Not called by `renderRichText`: a malformed heading should not stop a site
39
+ * from deploying, and Markdoc degrades to plain text rather than to nothing.
40
+ * Exposed so a project can decide otherwise.
41
+ */
42
+ export function validateRichText(source, options = {}) {
43
+ return Markdoc.validate(Markdoc.parse(source), options.config ?? {});
44
+ }
45
+ //# sourceMappingURL=richtext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"richtext.js","sourceRoot":"","sources":["../src/richtext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,OAAO,EAAE,EAAwC,MAAM,kBAAkB,CAAC;AAajF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAiC,EAAE,UAA2B,EAAE;IAC7F,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAE/C,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,UAA2B,EAAE;IAE7B,OAAO,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,UAA2B,EAAE;IAC5E,OAAO,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACvE,CAAC"}
package/dist/root.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Where the content root is, said once.
3
+ *
4
+ * The collections, the dev integration and the manifest writer all have to
5
+ * agree on this directory, and a disagreement between them produces an empty
6
+ * build rather than an error — the worst kind of failure. So the project states
7
+ * it once, in `defineSite({ root })`, and everything here resolves that one
8
+ * string the same way.
9
+ *
10
+ * Resolved against the **Astro project root**, not the working directory, which
11
+ * is what Astro's own `glob({ base })` does. A relative root therefore means
12
+ * the same thing whether the build was started from the project, from a
13
+ * workspace root, or by an editor extension. Absolute paths are left alone, for
14
+ * content that lives outside the project.
15
+ */
16
+ export declare const DEFAULT_CONTENT_ROOT = "./content";
17
+ export declare function resolveContentRoot(root: string | undefined, projectRoot?: URL): string;
18
+ //# sourceMappingURL=root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,WAAW,CAAC,EAAE,GAAG,GAAG,MAAM,CAetF"}
package/dist/root.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Where the content root is, said once.
3
+ *
4
+ * The collections, the dev integration and the manifest writer all have to
5
+ * agree on this directory, and a disagreement between them produces an empty
6
+ * build rather than an error — the worst kind of failure. So the project states
7
+ * it once, in `defineSite({ root })`, and everything here resolves that one
8
+ * string the same way.
9
+ *
10
+ * Resolved against the **Astro project root**, not the working directory, which
11
+ * is what Astro's own `glob({ base })` does. A relative root therefore means
12
+ * the same thing whether the build was started from the project, from a
13
+ * workspace root, or by an editor extension. Absolute paths are left alone, for
14
+ * content that lives outside the project.
15
+ */
16
+ import { isAbsolute, resolve } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ export const DEFAULT_CONTENT_ROOT = "./content";
19
+ export function resolveContentRoot(root, projectRoot) {
20
+ const configured = root ?? DEFAULT_CONTENT_ROOT;
21
+ if (isAbsolute(configured))
22
+ return configured;
23
+ // Only outside a loader or a hook — a plain unit test, or an Astro version
24
+ // that stops passing its config. The working directory is the best guess
25
+ // left, and it is right whenever the build was started from the project.
26
+ if (!projectRoot)
27
+ return resolve(configured);
28
+ // A base without a trailing slash would have its last segment replaced
29
+ // rather than appended to. Astro's `config.root` has one; nothing else is
30
+ // guaranteed to.
31
+ const base = projectRoot.href.endsWith("/") ? projectRoot.href : `${projectRoot.href}/`;
32
+ return fileURLToPath(new URL(configured, base));
33
+ }
34
+ //# sourceMappingURL=root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.js","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAEhD,MAAM,UAAU,kBAAkB,CAAC,IAAwB,EAAE,WAAiB;IAC5E,MAAM,UAAU,GAAG,IAAI,IAAI,oBAAoB,CAAC;IAEhD,IAAI,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IAE9C,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,IAAI,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;IAE7C,uEAAuE;IACvE,0EAA0E;IAC1E,iBAAiB;IACjB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,IAAI,GAAG,CAAC;IACxF,OAAO,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Where content files come from.
3
+ *
4
+ * Two lines wide on purpose. Everything above it — entry discovery, layout,
5
+ * parsing — is written against `read` and `list` and nothing else, so the same
6
+ * reader serves a checked-out repository during a build, an in-memory fixture
7
+ * in a test, and later a repository connector that answers from a provider API
8
+ * instead of a disk.
9
+ */
10
+ export interface DirectoryEntry {
11
+ name: string;
12
+ type: "file" | "directory";
13
+ }
14
+ export interface ContentSource {
15
+ /** A description of the origin, for error messages. */
16
+ readonly origin: string;
17
+ /** File contents as UTF-8 text, or null when there is no such file. */
18
+ read(path: string): Promise<string | null>;
19
+ /** Direct children of a directory; empty when the directory does not exist. */
20
+ list(path: string): Promise<DirectoryEntry[]>;
21
+ }
22
+ export declare class ContentSourceError extends Error {
23
+ name: string;
24
+ }
25
+ /**
26
+ * Reads a content directory in a checked-out repository.
27
+ *
28
+ * A missing file is not an error: an entry with no rich text simply has no
29
+ * `.mdoc` beside it, and a collection an editor has not filled in yet is an
30
+ * empty directory. Refusing to build over either would be wrong. Anything else
31
+ * — a permission problem, a corrupt file, a path that escapes the root — is
32
+ * raised, because it means the content the build was asked for is not there.
33
+ */
34
+ export declare function fileSystemSource(root: string): ContentSource;
35
+ /**
36
+ * A source built from a flat path-to-contents map, for tests and fixtures.
37
+ *
38
+ * Directories are inferred from the paths rather than declared, which keeps a
39
+ * fixture to the files it actually cares about.
40
+ */
41
+ export declare function memorySource(files: Record<string, string>, origin?: string): ContentSource;
42
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3C,+EAA+E;IAC/E,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CAC/C;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAClC,IAAI,SAAwB;CACtC;AAMD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CA6C5D;AAYD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,SAAW,GAAG,aAAa,CA8B5F"}
package/dist/source.js ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Where content files come from.
3
+ *
4
+ * Two lines wide on purpose. Everything above it — entry discovery, layout,
5
+ * parsing — is written against `read` and `list` and nothing else, so the same
6
+ * reader serves a checked-out repository during a build, an in-memory fixture
7
+ * in a test, and later a repository connector that answers from a provider API
8
+ * instead of a disk.
9
+ */
10
+ import { readdir, readFile } from "node:fs/promises";
11
+ import { isAbsolute, join, resolve, sep } from "node:path";
12
+ export class ContentSourceError extends Error {
13
+ name = "ContentSourceError";
14
+ }
15
+ /* ------------------------------------------------------------------ *
16
+ * Filesystem
17
+ * ------------------------------------------------------------------ */
18
+ /**
19
+ * Reads a content directory in a checked-out repository.
20
+ *
21
+ * A missing file is not an error: an entry with no rich text simply has no
22
+ * `.mdoc` beside it, and a collection an editor has not filled in yet is an
23
+ * empty directory. Refusing to build over either would be wrong. Anything else
24
+ * — a permission problem, a corrupt file, a path that escapes the root — is
25
+ * raised, because it means the content the build was asked for is not there.
26
+ */
27
+ export function fileSystemSource(root) {
28
+ const base = resolve(root);
29
+ const within = (path) => {
30
+ if (isAbsolute(path)) {
31
+ throw new ContentSourceError(`Content path "${path}" must be relative to the content root.`);
32
+ }
33
+ const full = resolve(base, path);
34
+ // `startsWith(base)` alone would accept a sibling called `content-old`.
35
+ if (full !== base && !full.startsWith(base + sep)) {
36
+ throw new ContentSourceError(`Content path "${path}" escapes the content root.`);
37
+ }
38
+ return full;
39
+ };
40
+ return {
41
+ origin: base,
42
+ async read(path) {
43
+ try {
44
+ return await readFile(within(path), "utf8");
45
+ }
46
+ catch (error) {
47
+ if (isMissing(error))
48
+ return null;
49
+ throw error;
50
+ }
51
+ },
52
+ async list(path) {
53
+ let items;
54
+ try {
55
+ items = await readdir(within(path), { withFileTypes: true });
56
+ }
57
+ catch (error) {
58
+ if (isMissing(error))
59
+ return [];
60
+ throw error;
61
+ }
62
+ return items
63
+ .filter((item) => item.isFile() || item.isDirectory())
64
+ // Sorted here rather than at each call site, so that a build does not
65
+ // depend on the order a filesystem happens to report.
66
+ .sort((a, b) => a.name.localeCompare(b.name))
67
+ .map((item) => ({ name: item.name, type: item.isFile() ? "file" : "directory" }));
68
+ },
69
+ };
70
+ }
71
+ /** ENOENT, and ENOTDIR for a path whose parent turned out to be a file. */
72
+ function isMissing(error) {
73
+ const code = error?.code;
74
+ return code === "ENOENT" || code === "ENOTDIR";
75
+ }
76
+ /* ------------------------------------------------------------------ *
77
+ * In memory
78
+ * ------------------------------------------------------------------ */
79
+ /**
80
+ * A source built from a flat path-to-contents map, for tests and fixtures.
81
+ *
82
+ * Directories are inferred from the paths rather than declared, which keeps a
83
+ * fixture to the files it actually cares about.
84
+ */
85
+ export function memorySource(files, origin = "memory") {
86
+ const normalise = (path) => path.replace(/^\.?\//, "").replace(/\/$/, "");
87
+ return {
88
+ origin,
89
+ async read(path) {
90
+ return files[normalise(path)] ?? null;
91
+ },
92
+ async list(path) {
93
+ const prefix = normalise(path) === "" ? "" : `${normalise(path)}/`;
94
+ const seen = new Map();
95
+ for (const key of Object.keys(files)) {
96
+ if (!key.startsWith(prefix))
97
+ continue;
98
+ const rest = key.slice(prefix.length);
99
+ if (rest === "")
100
+ continue;
101
+ const slash = rest.indexOf("/");
102
+ if (slash === -1)
103
+ seen.set(rest, "file");
104
+ else
105
+ seen.set(rest.slice(0, slash), "directory");
106
+ }
107
+ return [...seen]
108
+ .sort(([a], [b]) => a.localeCompare(b))
109
+ .map(([name, type]) => ({ name, type }));
110
+ },
111
+ };
112
+ }
113
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@micacms/astro",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "description": "Reads a Mica content repository as Astro Content Collections via the Content Layer API.",
6
+ "license": "MIT",
7
+ "author": "Alexander Müdespacher",
8
+ "keywords": [
9
+ "mica",
10
+ "cms",
11
+ "astro",
12
+ "astro-integration",
13
+ "content-layer",
14
+ "markdoc"
15
+ ],
16
+ "sideEffects": false,
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "dependencies": {
30
+ "@markdoc/markdoc": "^0.5.9",
31
+ "zod": "^3.25.76",
32
+ "@micacms/model": "0.0.2"
33
+ },
34
+ "peerDependencies": {
35
+ "astro": "^5.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.20.1",
39
+ "astro": "^5.13.0",
40
+ "typescript": "^5.7.3"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.json",
44
+ "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
45
+ "typecheck": "tsc -p tsconfig.test.json",
46
+ "test": "tsc -p tsconfig.json && node --test test/*.test.ts"
47
+ }
48
+ }