@micacms/astro 0.4.1 → 0.5.0-rc.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/README.md CHANGED
@@ -88,6 +88,12 @@ of its collections reloads that collection only — never a foreign loader.
88
88
  `transformRichText(source)` returns the renderable tree instead, for a project
89
89
  that wants to render with its own components.
90
90
 
91
+ ## Setup guides for coding agents
92
+
93
+ `setup/` holds the guides that https://micacms.com/setup.md sends a coding
94
+ agent to: a new or empty Astro project, and a project with content
95
+ collections. They ship with the package, so they match the installed version.
96
+
91
97
  ## Install
92
98
 
93
99
  ```bash
package/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * @micacms/astro — a Git repository as an Astro content source.
3
3
  *
4
- * // src/content.config.ts
4
+ * // src/content.config.ts — the model is in src/mica.ts
5
5
  * import { micaCollections } from "@micacms/astro";
6
- * import { defineSite } from "@micacms/core";
6
+ * import { site } from "./mica";
7
7
  *
8
- * export const site = defineSite({ collections: { … } });
9
- * export const collections = micaCollections(site, { root: "./content" });
8
+ * export const collections = micaCollections(site);
10
9
  *
11
10
  * From there the website uses ordinary Astro APIs — `getCollection("projects")`
12
11
  * — and knows nothing about how the content arrived.
@@ -50,11 +49,8 @@ export interface MicaCollectionsOptions {
50
49
  /**
51
50
  * Entry metadata attached to every collection document.
52
51
  *
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.
52
+ * No timestamps: in Git they are commit metadata, which a static build cannot
53
+ * read cheaply. A date an editor cares about belongs in the model as a `date()` field.
58
54
  *
59
55
  * Namespaced because these keys share a namespace with the customer's own
60
56
  * fields, and `position` is a plausible field name.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,QAAQ;IACvB,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;CAClB;AASD,yEAAyE;AACzE,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC,GAAG,OAAO;IAChD,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CACtB;AAED,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE;QACL,KAAK,IAAI,IAAI,CAAC;QACd,GAAG,CAAC,KAAK,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC;QACpF,IAAI,IAAI,MAAM,EAAE,CAAC;KAClB,CAAC;IACF,MAAM,EAAE;QACN,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC;IACF,IAAI,EAAE;QAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACtF;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC;IACvB;;;;;OAKG;IACH,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;KAC/E,CAAC;IACF,SAAS,CAAC,KAAK,EAAE;QACf,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC/B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC;CAChE;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,IAAI,IAAI;KAC3C,CAAC,IAAI,MAAM,CAAC,CAAC,aAAa,CAAC,GAAG,qBAAqB,CAClD,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACpD;CACF,GAAG;KACD,CAAC,IAAI,MAAM,CAAC,CAAC,YAAY,CAAC,GAAG,qBAAqB,CAAC,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACxF,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,SAAS,IAAI,EAC5C,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAAC,CAAC,CAAC,CAqJpB;AAuED,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,CAC5B,CAAC,SAAS,IAAI,EACd,CAAC,SAAS,MAAM,CAAC,CAAC,aAAa,CAAC,IAC9B,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,2CAA2C;AAC3C,MAAM,MAAM,iBAAiB,CAC3B,CAAC,SAAS,IAAI,EACd,CAAC,SAAS,MAAM,CAAC,CAAC,YAAY,CAAC,IAC7B,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,CAAC;AACpD,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * @micacms/astro — a Git repository as an Astro content source.
3
3
  *
4
- * // src/content.config.ts
4
+ * // src/content.config.ts — the model is in src/mica.ts
5
5
  * import { micaCollections } from "@micacms/astro";
6
- * import { defineSite } from "@micacms/core";
6
+ * import { site } from "./mica";
7
7
  *
8
- * export const site = defineSite({ collections: { … } });
9
- * export const collections = micaCollections(site, { root: "./content" });
8
+ * export const collections = micaCollections(site);
10
9
  *
11
10
  * From there the website uses ordinary Astro APIs — `getCollection("projects")`
12
11
  * — and knows nothing about how the content arrived.
@@ -17,7 +16,7 @@
17
16
  * Publishing is a commit, and CI builds the commit.
18
17
  */
19
18
  import { mkdir, readFile, writeFile } from "node:fs/promises";
20
- import { dirname, join, sep } from "node:path";
19
+ import { dirname, join, relative, sep } from "node:path";
21
20
  import { fieldsSchema, MANIFEST_PATH, readCollection, readSingleton, } from "@micacms/core";
22
21
  import { fileSystemSource } from "@micacms/core/node";
23
22
  import { z } from "zod";
@@ -113,7 +112,7 @@ export function micaCollections(site, options = {}) {
113
112
  });
114
113
  context.store.set({ id: entry.slug, data, digest: context.generateDigest(data) });
115
114
  }
116
- context.logger.info(`Loaded ${entries.length} ${def.label.toLowerCase()}`);
115
+ context.logger.info(`Loaded ${entries.length} ${entries.length === 1 ? "entry" : "entries"} of "${def.label}"`);
117
116
  };
118
117
  await publish(context);
119
118
  await read();
@@ -139,7 +138,7 @@ export function micaCollections(site, options = {}) {
139
138
  // Not fatal. A site is normally deployed before every settings
140
139
  // form has been filled in, and the page that reads this singleton
141
140
  // reports the absence far better than the loader can.
142
- context.logger.warn(`No content for the "${def.label}" settings yet.`);
141
+ context.logger.warn(`No content for "${def.label}" yet.`);
143
142
  return;
144
143
  }
145
144
  const parsed = await context.parseData({ id: name, data });
@@ -179,7 +178,7 @@ async function publishModel(site, root, logger) {
179
178
  return;
180
179
  await mkdir(dirname(target), { recursive: true });
181
180
  await writeFile(target, contents, "utf8");
182
- logger.info(`Wrote the content model to ${MANIFEST_PATH} — commit it for Mica to read.`);
181
+ logger.info(`Wrote the content model to ${relative(process.cwd(), target)} — commit it for Mica to read.`);
183
182
  }
184
183
  catch (error) {
185
184
  // A site that builds correctly should not fail over this, and a read-only
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @micacms/astro — a Git repository as an Astro content source.\n *\n * // src/content.config.ts — the model is in src/mica.ts\n * import { micaCollections } from \"@micacms/astro\";\n * import { site } from \"./mica\";\n *\n * export const collections = micaCollections(site);\n *\n * From there the website uses ordinary Astro APIs — `getCollection(\"projects\")`\n * — and knows nothing about how the content arrived.\n *\n * There is no CMS in this path. The loaders read files that are already in the\n * working tree, so a build needs a checkout and nothing else: no running\n * server, no token, no network. That is the point of the Git-native design.\n * Publishing is a commit, and CI builds the commit.\n */\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative, sep } from \"node:path\";\n\nimport {\n fieldsSchema,\n MANIFEST_PATH,\n readCollection,\n readSingleton,\n type Collection,\n type ContentSource,\n type InferCollection,\n type InferSingleton,\n type Singleton,\n type Site,\n} from \"@micacms/core\";\nimport { fileSystemSource } from \"@micacms/core/node\";\nimport { z } from \"zod\";\n\nimport { manifestFor } from \"./manifest.js\";\nimport { DEFAULT_CONTENT_ROOT, resolveContentRoot } from \"./root.js\";\n\nexport interface MicaCollectionsOptions {\n /**\n * Overrides `defineSite({ root })`, which is where the content root belongs.\n *\n * Relative paths resolve against the Astro project root, like Astro's own\n * `glob({ base })`. An absolute path is used as given — which is what a\n * project whose content is a separate repository, checked out by CI\n * somewhere else, wants.\n */\n root?: string;\n /** Overrides `root` entirely. Used by tests, and by a non-filesystem source. */\n source?: ContentSource;\n /**\n * Turns off publishing the model to `<root>/.mica/schema.json`.\n *\n * Only for a project that is not edited through Mica at all: without that\n * file in the repository, Mica has no model and refuses to start.\n */\n publishModel?: false;\n /**\n * How long to wait after a content change before reloading, in dev.\n *\n * Saving one entry touches several files — `index.json` and every `.mdoc`\n * beside it — and a Git checkout touches all of them at once. One reload\n * after the burst is both faster and less confusing than one per file.\n */\n debounceMs?: number;\n}\n\n/**\n * Entry metadata attached to every collection document.\n *\n * No timestamps: in Git they are commit metadata, which a static build cannot\n * read cheaply. A date an editor cares about belongs in the model as a `date()` field.\n *\n * Namespaced because these keys share a namespace with the customer's own\n * fields, and `position` is a plausible field name.\n */\nexport interface MicaMeta {\n /** The entry slug, which is its identity in the repository. */\n id: string;\n slug: string;\n collection: string;\n /** Index in the collection's order, manual when `_order.json` says so. */\n position: number;\n}\n\nconst metaSchema = z.object({\n id: z.string(),\n slug: z.string(),\n collection: z.string(),\n position: z.number(),\n});\n\n/** What a document looks like inside Astro: the payload plus `_mica`. */\nexport type AstroDocument<T> = T & { _mica: MicaMeta };\n\n/**\n * The shape Astro's config loader expects.\n *\n * Deliberately not built with `defineCollection` from `astro:content`: that is a\n * virtual module, resolvable only inside an Astro app's module graph, and this\n * package is pre-compiled. `defineCollection` does nothing more than stamp this\n * `type`, so producing the object directly keeps the package importable\n * anywhere — including from a plain unit test.\n */\nexport interface AstroCollectionConfig<T = unknown> {\n type: \"content_layer\";\n loader: ContentLoader;\n /**\n * Typed as `ZodType<T>` rather than a bare `ZodObject`, because Astro derives\n * `CollectionEntry<\"projects\">[\"data\"]` from `z.infer` of whatever this is.\n * Left generic, every field in every template would degrade to `any` — and\n * the point of the shared model is that renaming a field breaks the build.\n */\n schema: z.ZodType<T>;\n}\n\n/** The subset of Astro's `Loader` contract this package uses. */\nexport interface ContentLoader {\n name: string;\n load(context: LoaderContext): Promise<void>;\n}\n\nexport interface LoaderContext {\n store: {\n clear(): void;\n set(entry: { id: string; data: Record<string, unknown>; digest?: string }): boolean;\n keys(): string[];\n };\n logger: {\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n debug(message: string): void;\n };\n meta: { get(key: string): string | undefined; set(key: string, value: string): void };\n /**\n * Astro's resolved config. Optional because this interface is a hand-written\n * subset and the tests build a context by hand; `config.root` is what turns a\n * relative content root into a path.\n */\n config?: { root: URL };\n /**\n * Present only while `astro dev` runs. Astro's watcher follows the module\n * graph, and content files are not in it — no page imports\n * `projects/haus-a/index.json` — so each loader watches its own directory\n * here, exactly as Astro's own `glob` loader does.\n */\n watcher?: {\n add(path: string): void;\n on(event: \"add\" | \"change\" | \"unlink\", handler: (path: string) => void): void;\n };\n parseData(input: {\n id: string;\n data: Record<string, unknown>;\n }): Promise<Record<string, unknown>>;\n generateDigest(data: Record<string, unknown> | string): string;\n}\n\nexport type MicaCollections<S extends Site> = {\n [K in keyof S[\"collections\"]]: AstroCollectionConfig<\n AstroDocument<InferCollection<S[\"collections\"][K]>>\n >;\n} & {\n [K in keyof S[\"singletons\"]]: AstroCollectionConfig<InferSingleton<S[\"singletons\"][K]>>;\n};\n\nexport function micaCollections<S extends Site>(\n site: S,\n options: MicaCollectionsOptions = {},\n): MicaCollections<S> {\n /**\n * Everything below is resolved on first load rather than here, because the\n * Astro project root is something only a loader is told — and it is what\n * turns `./content` into a directory. Memoised: one root and one source,\n * however many collections and however many reloads.\n */\n let root: string | undefined;\n let source: ContentSource | undefined;\n\n const rootFor = (context: LoaderContext): string =>\n (root ??= resolveContentRoot(options.root ?? site.root, context.config?.root));\n\n const sourceFor = (context: LoaderContext): ContentSource =>\n (source ??= options.source ?? fileSystemSource(rootFor(context)));\n\n /** Published once per process; a model change restarts the dev server. */\n let publishing: Promise<void> | undefined;\n\n const publish = (context: LoaderContext): Promise<void> =>\n (publishing ??=\n options.publishModel === false || options.source\n ? Promise.resolve()\n : publishModel(site, rootFor(context), context.logger));\n\n /** Collections whose watcher is already registered, so a reload adds none. */\n const watching = new Set<string>();\n\n const watch = (context: LoaderContext, name: string, reload: () => Promise<void>): void => {\n const watcher = context.watcher;\n if (!watcher || options.source || watching.has(name)) return;\n watching.add(name);\n\n const base = rootFor(context);\n watcher.add(base);\n\n // A collection lives in `<root>/<name>/`; a singleton is either\n // `<root>/<name>.json` or `<root>/<name>/…`. Both are covered by this\n // prefix, and nothing outside it can concern this loader — which is what\n // keeps one collection's save from re-reading every other one.\n const prefix = join(base, name);\n const mine = (path: string): boolean =>\n path === `${prefix}.json` || path.startsWith(prefix + sep);\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const onChange = (path: string): void => {\n // The manifest is this package's own output; reloading content because\n // the model was written is a loop waiting to happen.\n if (path.endsWith(MANIFEST_PATH) || !mine(path)) return;\n\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n // Content that fails its own schema lands here. The dev server has to\n // survive it: the next save is very likely the fix.\n void reload().catch((error: unknown) => {\n context.logger.error(`Could not reload content: ${describe(error)}`);\n });\n }, options.debounceMs ?? 100);\n // A pending reload is never a reason to keep the process alive.\n timer.unref?.();\n };\n\n watcher.on(\"change\", onChange);\n watcher.on(\"add\", onChange);\n watcher.on(\"unlink\", onChange);\n };\n\n const collections = {} as Record<string, AstroCollectionConfig<unknown>>;\n\n for (const [name, def] of Object.entries(site.collections) as [string, Collection][]) {\n collections[name] = {\n type: \"content_layer\",\n // `storage` rather than `output`: a media reference is a path into the\n // repository at rest and a path into the repository once loaded. There\n // is no expansion step left to tell the two apart.\n schema: fieldsSchema(def.fields, \"storage\").extend({ _mica: metaSchema }),\n loader: {\n name: `mica:${name}`,\n load: async (context) => {\n const read = async (): Promise<void> => {\n const source = sourceFor(context);\n const entries = await guard(context, source, () => readCollection(source, def, name));\n\n // A full replace, not a merge: an entry deleted from the repository\n // has to disappear from the build.\n context.store.clear();\n\n for (const entry of entries) {\n const meta: MicaMeta = {\n id: entry.slug,\n slug: entry.slug,\n collection: name,\n position: entry.position,\n };\n\n const data = await context.parseData({\n id: entry.slug,\n data: { ...entry.data, _mica: meta },\n });\n context.store.set({ id: entry.slug, data, digest: context.generateDigest(data) });\n }\n\n context.logger.info(`Loaded ${entries.length} ${entries.length === 1 ? \"entry\" : \"entries\"} of \"${def.label}\"`);\n };\n\n await publish(context);\n await read();\n watch(context, name, read);\n },\n },\n };\n }\n\n // A singleton becomes a one-entry collection whose id is its name, so the\n // website reaches it with the same `getEntry` it uses for everything else.\n for (const [name, def] of Object.entries(site.singletons) as [string, Singleton][]) {\n collections[name] = {\n type: \"content_layer\",\n schema: fieldsSchema(def.fields, \"storage\"),\n loader: {\n name: `mica:${name}`,\n load: async (context) => {\n const read = async (): Promise<void> => {\n const source = sourceFor(context);\n const data = await guard(context, source, () => readSingleton(source, def, name));\n\n context.store.clear();\n if (data === null) {\n // Not fatal. A site is normally deployed before every settings\n // form has been filled in, and the page that reads this singleton\n // reports the absence far better than the loader can.\n context.logger.warn(`No content for \"${def.label}\" yet.`);\n return;\n }\n\n const parsed = await context.parseData({ id: name, data });\n context.store.set({ id: name, data: parsed, digest: context.generateDigest(parsed) });\n };\n\n await publish(context);\n await read();\n watch(context, name, read);\n },\n },\n };\n }\n\n return collections as MicaCollections<S>;\n}\n\n/**\n * Writes the model into the content root, for Mica to read back.\n *\n * Mica holds no content model: the Astro project is the only place a collection\n * is defined, and this is how it says so. The file goes *inside the content\n * root* so that it travels to Mica the way content does — through Git and the\n * repository connector — with nothing to mount and no second channel to keep in\n * step. Commit it.\n *\n * Done from a loader rather than an integration because this is the moment both\n * facts are in hand: the model, and the project root that turns `./content`\n * into a directory. It also means `astro.config` needs no Mica in it, which is\n * what lets a project define Mica collections and ordinary Astro ones in the\n * same `content.config.ts`.\n */\nasync function publishModel(\n site: Site,\n root: string,\n logger: LoaderContext[\"logger\"],\n): Promise<void> {\n const target = join(root, MANIFEST_PATH);\n const contents = `${JSON.stringify(manifestFor(site), null, 2)}\\n`;\n\n try {\n // Only when it differs. The content root is watched in dev, and rewriting\n // an identical file on every start would ask for a reload for no reason.\n if ((await readFile(target, \"utf8\").catch(() => null)) === contents) return;\n\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, contents, \"utf8\");\n logger.info(`Wrote the content model to ${relative(process.cwd(), target)} — commit it for Mica to read.`);\n } catch (error) {\n // A site that builds correctly should not fail over this, and a read-only\n // checkout is a normal way to build one in CI. Mica reports a missing model\n // itself, and far more usefully.\n logger.warn(`Could not write the content model: ${describe(error)}`);\n }\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Reports a read failure against the content root that produced it.\n *\n * A build failing on unreadable content is correct — publishing a half-empty\n * site would be worse — but the message has to name the directory that was\n * read, because the usual cause is a content root pointing at the wrong place.\n */\nasync function guard<T>(\n context: LoaderContext,\n source: ContentSource,\n read: () => Promise<T>,\n): Promise<T> {\n try {\n return await read();\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n context.logger.error(detail);\n\n throw new Error(`Could not read content from ${source.origin}.\\n ${detail}`, { cause: error });\n }\n}\n\n/* ------------------------------------------------------------------ *\n * Types for the website\n * ------------------------------------------------------------------ */\n\n/** The document type for one collection, as it appears in Astro. */\nexport type CollectionDocument<\n S extends Site,\n K extends keyof S[\"collections\"],\n> = AstroDocument<InferCollection<S[\"collections\"][K]>>;\n\n/** The document type for one singleton. */\nexport type SingletonDocument<\n S extends Site,\n K extends keyof S[\"singletons\"],\n> = InferSingleton<S[\"singletons\"][K]>;\n\nexport { manifestFor } from \"./manifest.js\";\nexport { DEFAULT_CONTENT_ROOT, resolveContentRoot };\nexport {\n renderRichText,\n transformRichText,\n validateRichText,\n type RichTextOptions,\n} from \"./richtext.js\";\n\n/**\n * Re-exported for convenience, so a project that only depends on this package\n * can still build a source or read a collection. They are defined in\n * `@micacms/core`, where the Mica server can reach them too.\n */\nexport {\n ContentReadError,\n ContentSourceError,\n ORDER_FILE,\n memorySource,\n readCollection,\n readSingleton,\n type ContentSource,\n type DirectoryEntry,\n} from \"@micacms/core\";\nexport { fileSystemSource } from \"@micacms/core/node\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAoC,MAAM,eAAe,CAAC;AAEjF,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"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAoC,MAAM,eAAe,CAAC;AAEjF,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","sourcesContent":["/**\n * The protocol manifest this project publishes.\n *\n * Its own module rather than a function in `index.ts`, so that the integration\n * can emit a manifest without importing the loaders, and the loaders can be\n * imported without pulling in the integration. The two files would otherwise\n * import each other.\n */\n\nimport { createManifest, type ProtocolManifest, type Site } from \"@micacms/core\";\n\n/** Replaced at publish time; the fallback keeps a workspace build honest. */\nconst PACKAGE_VERSION = \"0.0.1\";\n\n/**\n * Mica holds no model of its own. The Astro project is the only place a\n * collection is defined, and this is how it says so.\n */\nexport function manifestFor(site: Site): ProtocolManifest {\n return createManifest(site, `@micacms/astro@${PACKAGE_VERSION}`);\n}\n"]}
@@ -1 +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"}
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","sourcesContent":["/**\n * Rendering a rich-text field.\n *\n * A `richText()` field is persisted as Markdoc, and this is what turns it into\n * HTML at build time. It runs during `astro build` only; the deployed page is\n * static, and no visitor's browser ever sees Markdoc.\n *\n * Markdoc rather than Markdown-with-HTML for one reason that matters: a Markdoc\n * document can only produce what the configuration allows. An undeclared\n * `{% tag %}` renders as nothing rather than as itself, and raw HTML is inert\n * unless it is explicitly permitted — which it is not here. So the canonical\n * content format cannot carry markup that escapes the site's own styling, and a\n * compromised or careless editor session cannot inject script into a build.\n */\n\nimport Markdoc, { type Config, type RenderableTreeNode } from \"@markdoc/markdoc\";\n\nexport interface RichTextOptions {\n /**\n * Extra Markdoc configuration: custom tags, nodes or variables.\n *\n * The place a project adds a callout or a gallery block without giving up the\n * closed set that makes the format safe. A tag must be declared here to\n * render at all.\n */\n config?: Config;\n}\n\n/**\n * Markdoc source to an HTML string.\n *\n * Returns an empty string for empty input, so a caller can render the result\n * directly without a null check ahead of it.\n */\nexport function renderRichText(source: string | null | undefined, options: RichTextOptions = {}): string {\n if (!source || source.trim() === \"\") return \"\";\n\n const tree = transformRichText(source, options);\n return Markdoc.renderers.html(tree);\n}\n\n/**\n * The intermediate renderable tree, for a project that wants to render with\n * its own components rather than to an HTML string.\n */\nexport function transformRichText(\n source: string,\n options: RichTextOptions = {},\n): RenderableTreeNode {\n return Markdoc.transform(Markdoc.parse(source), options.config ?? {});\n}\n\n/**\n * Problems Markdoc found in a document, for a build that wants to be strict.\n *\n * Not called by `renderRichText`: a malformed heading should not stop a site\n * from deploying, and Markdoc degrades to plain text rather than to nothing.\n * Exposed so a project can decide otherwise.\n */\nexport function validateRichText(source: string, options: RichTextOptions = {}) {\n return Markdoc.validate(Markdoc.parse(source), options.config ?? {});\n}\n"]}
package/dist/root.js.map CHANGED
@@ -1 +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"}
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","sourcesContent":["/**\n * Where the content root is, said once.\n *\n * The collections, the dev integration and the manifest writer all have to\n * agree on this directory, and a disagreement between them produces an empty\n * build rather than an error — the worst kind of failure. So the project states\n * it once, in `defineSite({ root })`, and everything here resolves that one\n * string the same way.\n *\n * Resolved against the **Astro project root**, not the working directory, which\n * is what Astro's own `glob({ base })` does. A relative root therefore means\n * the same thing whether the build was started from the project, from a\n * workspace root, or by an editor extension. Absolute paths are left alone, for\n * content that lives outside the project.\n */\n\nimport { isAbsolute, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const DEFAULT_CONTENT_ROOT = \"./content\";\n\nexport function resolveContentRoot(root: string | undefined, projectRoot?: URL): string {\n const configured = root ?? DEFAULT_CONTENT_ROOT;\n\n if (isAbsolute(configured)) return configured;\n\n // Only outside a loader or a hook — a plain unit test, or an Astro version\n // that stops passing its config. The working directory is the best guess\n // left, and it is right whenever the build was started from the project.\n if (!projectRoot) return resolve(configured);\n\n // A base without a trailing slash would have its last segment replaced\n // rather than appended to. Astro's `config.root` has one; nothing else is\n // guaranteed to.\n const base = projectRoot.href.endsWith(\"/\") ? projectRoot.href : `${projectRoot.href}/`;\n return fileURLToPath(new URL(configured, base));\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micacms/astro",
3
- "version": "0.4.1",
3
+ "version": "0.5.0-rc.2",
4
4
  "type": "module",
5
5
  "description": "Reads a Mica content repository as Astro Content Collections via the Content Layer API.",
6
6
  "license": "MIT",
@@ -24,12 +24,13 @@
24
24
  }
25
25
  },
26
26
  "files": [
27
- "dist"
27
+ "dist",
28
+ "setup"
28
29
  ],
29
30
  "dependencies": {
30
31
  "@markdoc/markdoc": "^0.5.9",
31
32
  "zod": "^4.3.6",
32
- "@micacms/core": "0.4.1"
33
+ "@micacms/core": "0.5.0-rc.2"
33
34
  },
34
35
  "peerDependencies": {
35
36
  "astro": "^6.0.0 || ^7.0.0"
@@ -0,0 +1,211 @@
1
+ # Move Astro content collections to Mica
2
+
3
+ For an Astro project whose content is in Astro content collections, usually
4
+ Markdown or MDX files with frontmatter. `SETUP.md` has already installed the
5
+ packages. The commands are in [reference.md](./reference.md#commands).
6
+
7
+ Mica stores content in its own format (JSON and Markdoc in `content/`), so the
8
+ entries are converted once. The website then reads them through
9
+ `micaCollections()`. Collections that Mica does not manage stay as they are.
10
+
11
+ ## 1. Prepare
12
+
13
+ - The Git working tree must be clean (`git status`). If it is not, ask the
14
+ user to commit or stash first.
15
+ - Ask the user whether to work on a new branch, and make it if they agree.
16
+ - Build the website once, and copy the result to a temporary directory
17
+ outside the project. Step 8 compares with it.
18
+
19
+ ```bash
20
+ npx astro build
21
+ cp -r dist <temporary directory>/dist-before
22
+ ```
23
+
24
+ ## 2. Make an inventory
25
+
26
+ Read `src/content.config.*` (or `src/content/config.*`), every page, layout and
27
+ endpoint that calls `getCollection`, `getEntry`, `render` or `reference`, and
28
+ the integrations in `astro.config.*`. For each collection, find:
29
+
30
+ - the loader and the files: `glob()` with `.md`, `.mdx`, `.json` or `.yaml`, or
31
+ something else;
32
+ - the schema, field by field;
33
+ - the number of entries, and whether the ids have folders (`2024/hello`);
34
+ - what the bodies contain: plain Markdown, raw HTML, footnotes, MDX imports and
35
+ components, images with relative paths;
36
+ - the pages and URLs that use the collection.
37
+
38
+ ## 3. Ask the user
39
+
40
+ Show the inventory as a short table, and ask:
41
+
42
+ 1. Which collections should Mica manage? Content that only developers change,
43
+ such as documentation in the repository, can stay an Astro collection.
44
+ 2. The model you propose for each collection, mapped as in step 4.
45
+ 3. Every case where content cannot move without a change:
46
+ - MDX components in a body. Markdoc does not run them. Options: a Markdoc
47
+ tag that the website declares and renders, a field, or keep that
48
+ collection in Astro. Remove the `import` lines too, and the text that
49
+ refers to a component you drop.
50
+ - Raw HTML in a body. Markdoc does not render it. Options: Markdown (`<b>`
51
+ becomes `**`), a Unicode character, a Markdoc tag, or remove it. Some tags
52
+ have no Markdown form (`<sub>`, `<sup>`, `<kbd>`, `<mark>`). Do not change
53
+ HTML inside code blocks or inline code: that is text.
54
+ - Footnotes (`[^1]`). Markdoc shows them as text. Options: a normal
55
+ paragraph or a link. Remove the `[^1]` markers from the text too.
56
+ - Images in a body (`![](./photo.jpg)`). The rich-text editor has no images.
57
+ Options: a `media()` field or a `repeater()` with images, or files in
58
+ `public/` with absolute URLs, which Astro does not optimize.
59
+ - Ids with folders, or ids that are not slugs. They become flat slugs, so
60
+ the URL can change. Options: keep the URL with Astro `redirects`, or
61
+ accept the new URL.
62
+ - A free list of strings, such as tags. See
63
+ [reference.md](./reference.md#the-content-model).
64
+ - Headings lose their `id` and code blocks lose their highlighting, unless
65
+ the website adds them. See
66
+ [reference.md](./reference.md#what-markdoc-renders-differently-from-astros-markdown).
67
+ 4. Should editors put a collection in a manual order?
68
+
69
+ Wait for the answers.
70
+
71
+ ## 4. Map the schema to the model
72
+
73
+ | Astro schema | Mica field |
74
+ | --- | --- |
75
+ | `z.string()` | `text()`, or `textarea()` for long plain text |
76
+ | `z.string().url()` / `.email()` | `url()` / `email()` |
77
+ | `z.number()` | `number()` |
78
+ | `z.boolean()` | `boolean()` |
79
+ | `z.date()`, `z.coerce.date()` | `date()` (stored as `"YYYY-MM-DD"`) |
80
+ | `z.enum([...])` | `select({ options })` |
81
+ | `z.array(z.enum([...]))` | `select({ multiple: true, options })` |
82
+ | `image()` | `media({ accept: "image" })` |
83
+ | `reference("authors")` | `relation({ collection: "authors" })` |
84
+ | `z.object({...})` | `object({ fields })` |
85
+ | `z.array(z.object({...}))` | `repeater({ fields })` |
86
+ | `.optional()` | the field without `required: true` |
87
+ | the Markdown body | `richText()`, usually named `body` |
88
+ | the entry id | a `slug()` field |
89
+
90
+ Give the Mica collection the same name as the Astro collection, so that
91
+ `getCollection("blog")` in the pages keeps working. The names must be
92
+ lowerCamelCase.
93
+
94
+ ## 5. Add the model
95
+
96
+ 1. Run `npx mica init`. It keeps your `src/content.config.ts` and prints the
97
+ lines to add.
98
+ 2. Replace the starter model in `src/mica.ts` (`news` and `settings`) with the
99
+ model the user agreed to.
100
+ 3. In `src/content.config.ts`, remove the definitions of the collections that
101
+ Mica now manages, and add `...micaCollections(site)`. Keep the others.
102
+ 4. Run `npx astro sync`. It writes `content/.mica/schema.json`, which the
103
+ conversion script reads. The pages can fail now; step 7 fixes them.
104
+
105
+ ## 6. Convert the entries
106
+
107
+ Write a conversion script inside the project, for example
108
+ `scripts/convert-to-mica.mjs`. Use `writeEntry` from
109
+ [reference.md](./reference.md#write-entries-with-a-script). For each entry,
110
+ the script:
111
+
112
+ 1. Reads the file and its frontmatter. For YAML, use a parser that the project
113
+ can already import (Astro depends on `yaml`), or install one for the
114
+ conversion only and remove it in step 9.
115
+ 2. Makes the slug from the id: lowercase, folders and other characters turned
116
+ into single hyphens. Two ids must not give the same slug.
117
+ 3. Builds `data` with every field of the model, in the stored form:
118
+ - a date from the text in the frontmatter, as `"YYYY-MM-DD"`. Take an ISO
119
+ date as it is written. Parse any other text in UTC, for example
120
+ `` new Date(`${text} UTC`).toISOString().slice(0, 10) ``, and compare a few
121
+ results with the source. Never format a local-time `Date`: in a time zone
122
+ east of UTC, that gives the day before.
123
+ - a relation as the slug of the other entry;
124
+ - the body as a Markdoc string, changed as the user decided in step 3;
125
+ - the slug field equal to the slug.
126
+ 4. Copies the image of each `image()` field to
127
+ `content/<collection>/<slug>/media/<file>`, and stores `media/<file>` in the
128
+ field. The path in the frontmatter is relative to the source entry's file.
129
+ Body images go where the user decided in step 3.
130
+ 5. Calls `writeEntry(collection, slug, data)`.
131
+ 6. Prints a line for each entry, and a list of everything it changed or could
132
+ not convert.
133
+
134
+ Keep the source files. Do not delete anything in this step.
135
+
136
+ Run the script, then:
137
+
138
+ ```bash
139
+ npx mica check
140
+ ```
141
+
142
+ Fix the script, not the output, until `mica check` reports 0 errors. Before
143
+ each run, delete the converted collection's directory in `content/` (not
144
+ `content/.mica/`), so that no file from an earlier run stays. Read every warning: "Not read" means that an
145
+ entry or a file is lost, and a Markdoc warning means that some text does not
146
+ render as before. If you cannot fix a warning, list it in the report with the
147
+ reason.
148
+
149
+ ## 7. Change the pages
150
+
151
+ Look at every file from the inventory: pages, layouts, components and
152
+ endpoints such as `rss.xml.js`. See
153
+ [reference.md](./reference.md#render-the-content-in-astro).
154
+
155
+ - `render(entry)` and `<Content />` become
156
+ `<div set:html={renderRichText(entry.data.body)} />`.
157
+ - A date was a `Date` and is now a `"YYYY-MM-DD"` string:
158
+ - Format it in UTC. If a formatter is shared, check its other callers: a
159
+ `Date` made in local time now shows the day before.
160
+ - Sort with `b.data.pubDate.localeCompare(a.data.pubDate)`. Subtracting
161
+ strings gives `NaN`, and the list is then not sorted, with no error.
162
+ - Where an API needs a `Date`, such as the RSS `pubDate`, pass
163
+ `new Date(entry.data.pubDate)`.
164
+ - An image was `ImageMetadata` and is now a path from the content root. Create
165
+ `src/lib/media.ts` from [reference.md](./reference.md#images), and use
166
+ `mediaImage()`.
167
+ - A reference was `{ collection, id }` and is now a slug:
168
+ `getEntry("authors", entry.data.author)`.
169
+ - `entry.id` is the slug. If ids had folders, update the links, and add
170
+ `redirects` in `astro.config` if the user wants the old URLs to keep working.
171
+ - A spread such as `{...post.data}` now also passes `_mica` and the new types.
172
+ Pass the fields one by one.
173
+ - A layout or component typed with `CollectionEntry<"blog">["data"]` can also
174
+ be used by a page that is not an entry, with the old types. Give it its own
175
+ props type.
176
+
177
+ ## 8. Compare the website
178
+
179
+ ```bash
180
+ npx astro build
181
+ ```
182
+
183
+ Compare `dist` with `dist-before` from step 1: the list of HTML files, and two
184
+ or three converted pages. Every page that is missing or new must have a
185
+ reason: a changed URL with a redirect, or a decision of the user. Expect these
186
+ differences, and tell the user about them:
187
+
188
+ - headings without `id`, and code blocks without highlighting;
189
+ - body images from `public/`, which Astro does not optimize;
190
+ - dates that showed the local day and now show the stored day, and times in
191
+ `<time datetime>` and the RSS feed that are now midnight UTC.
192
+
193
+ ## 9. Clean up
194
+
195
+ Ask the user before each of these:
196
+
197
+ - Delete the source files of the converted collections, and the images that
198
+ are now copies in `content/`, unless another file still imports them.
199
+ - Remove integrations and dependencies that only the old collections needed,
200
+ such as `@astrojs/mdx` when no MDX is left, and those that only the
201
+ conversion needed. Change text in the content that describes what you
202
+ removed.
203
+ - Delete the conversion script, or keep it for the record.
204
+
205
+ Then run `npx mica check` and `npx astro build` once more.
206
+
207
+ ## 10. Finish
208
+
209
+ Follow [finish.md](./finish.md). In the report, list each collection, the
210
+ number of entries before and after, and everything that changed or was not
211
+ converted.
@@ -0,0 +1,49 @@
1
+ # Finish the setup
2
+
3
+ ## 1. Check
4
+
5
+ All three must pass:
6
+
7
+ ```bash
8
+ npx astro sync
9
+ npx mica check # 0 errors
10
+ npx astro build
11
+ ```
12
+
13
+ If the project has `@astrojs/check`, run `npx astro check` too. Do not install
14
+ it only for this. Without it, nothing checks the types in `.astro` files: tell
15
+ the user.
16
+
17
+ Explain each warning of `mica check` to the user, or fix it.
18
+
19
+ ## 2. Add notes for the next agent
20
+
21
+ Add this section to the project's agent instructions: `AGENTS.md`,
22
+ `CLAUDE.md` or the file the project already uses. If `CLAUDE.md` is a link to
23
+ `AGENTS.md`, edit `AGENTS.md`. Make the file if there is none. Change the
24
+ paths if the project uses others.
25
+
26
+ ```markdown
27
+ ## Content (Mica)
28
+
29
+ - The content model is `src/mica.ts`. The content is JSON and Markdoc in
30
+ `content/`; editors change it with `npx mica`.
31
+ - After a change to `src/mica.ts`, run `npx astro sync` and `npx mica check`,
32
+ and commit `content/.mica/schema.json` with the change.
33
+ - Renaming or removing a field or a collection does not move the stored
34
+ content. Change the content files in the same commit, or ask first.
35
+ - Before a commit that changes `content/`, `npx mica check` must report 0 errors.
36
+ ```
37
+
38
+ ## 3. Tell the user
39
+
40
+ - What you added and changed, file by file.
41
+ - The decisions they made, and what was not converted, if anything.
42
+ - What to commit: every file that `git status` shows as new or changed,
43
+ including `content/.mica/schema.json`, `package.json` and the lock file. Do
44
+ not commit for them unless they asked.
45
+ - How to edit: `npx mica` opens the editor on this computer. A save writes
46
+ files in `content/`; Publish commits them. `@micacms/editor` is a dev
47
+ dependency, so on another clone, run `npm install` first.
48
+ - That a self-hosted editor for other people is described at
49
+ https://micacms.com/docs/self-hosting/.
@@ -0,0 +1,73 @@
1
+ # Set up Mica in a new or empty Astro project
2
+
3
+ For an Astro project that has no content collections yet. `SETUP.md` has
4
+ already created the project if there was none, and installed the packages.
5
+ The commands are in [reference.md](./reference.md#commands).
6
+
7
+ ## 1. Ask the user about the content
8
+
9
+ Ask these questions, and wait for the answers:
10
+
11
+ 1. What is the website for, and which pages does it need?
12
+ 2. Which content do editors change? For each kind, ask:
13
+ - Are there many of them (a collection: articles, projects, team members)
14
+ or one (a singleton: site settings, the home page, the navigation)?
15
+ - Which fields does it have? Which are required?
16
+ - Does it have long formatted text (rich text) or images?
17
+ 3. Do entries refer to other entries? For example, an article has an author
18
+ from the team.
19
+ 4. Should editors put a collection in a manual order?
20
+ 5. Do images need alt text that editors write? A media field has none, so
21
+ each image that carries meaning needs a `text()` field beside it.
22
+ 6. Do they want example content, so that the pages show something at once?
23
+
24
+ Suggest a model from the answers, as a short list of collections, singletons
25
+ and fields. Change it until the user agrees.
26
+
27
+ ## 2. Add Mica to the project
28
+
29
+ ```bash
30
+ npx mica init
31
+ ```
32
+
33
+ It writes `src/mica.ts` (a starter model) and `src/content.config.ts`. It
34
+ never overwrites a file. If `src/content.config.ts` already exists, it prints
35
+ the lines to add. Add them so that the existing collections stay.
36
+
37
+ ## 3. Write the model
38
+
39
+ Replace the starter model in `src/mica.ts` (`news` and `settings`) with the
40
+ model the user agreed to. See
41
+ [reference.md](./reference.md#the-content-model) for the field types and the
42
+ rules. Then publish it and check it:
43
+
44
+ ```bash
45
+ npx astro sync
46
+ npx mica check
47
+ ```
48
+
49
+ `astro sync` fails with a clear message when the model breaks a rule. Fix
50
+ `src/mica.ts` and run it again. Before there is content, `mica check` warns
51
+ "No content for … yet" for each singleton. That is expected until step 4.
52
+
53
+ ## 4. Add content (if the user wants examples)
54
+
55
+ Write one or two entries per collection, and fill in each singleton, as files
56
+ in `content/`. See [reference.md](./reference.md#the-files-on-disk). Use the
57
+ script in [reference.md](./reference.md#write-entries-with-a-script) for
58
+ entries, singletons and the manual order.
59
+
60
+ Then run `npx mica check` and fix every error.
61
+
62
+ ## 5. Make the pages
63
+
64
+ Make the pages that the user asked for. For each collection, usually a list
65
+ page and a page per entry. Use the singletons in the layout, for example the
66
+ site name. See [reference.md](./reference.md#render-the-content-in-astro) for
67
+ rich text, images, relations and dates.
68
+
69
+ Keep the design simple unless the user asked for more.
70
+
71
+ ## 6. Finish
72
+
73
+ Follow [finish.md](./finish.md).
@@ -0,0 +1,9 @@
1
+ # Move content from WordPress or another CMS
2
+
3
+ TODO: this guide is not written yet.
4
+
5
+ Tell the user that Mica has no guide for this yet. Then offer one of these:
6
+
7
+ - Set up Mica with [new-project.md](./new-project.md) first, and move the
8
+ content in a second step together with the user.
9
+ - Wait for the guide. Ask the user to follow the Mica releases.
@@ -0,0 +1,252 @@
1
+ # Mica reference for setup
2
+
3
+ The guides link here. Read the section that a step names.
4
+
5
+ ## Commands
6
+
7
+ | Command | What it does |
8
+ | --- | --- |
9
+ | `npx mica init` | Writes a starter `src/mica.ts` and `src/content.config.ts`. Never overwrites a file |
10
+ | `npx astro sync` | Runs the Mica loaders, which write the model to `content/.mica/schema.json` |
11
+ | `npx mica check` | Checks every entry against `content/.mica/schema.json`. Exit code 1 on an error |
12
+ | `npx mica` | Opens the editor in the browser. Only for the user; do not run it yourself |
13
+
14
+ `mica check` reads the model from `content/.mica/schema.json`. After every
15
+ change to `src/mica.ts`, run `npx astro sync` before `npx mica check`.
16
+
17
+ ## The content model
18
+
19
+ `src/mica.ts` exports one `defineSite(...)`:
20
+
21
+ ```ts
22
+ import { collection, defineSite, singleton, slug, text } from "@micacms/core";
23
+
24
+ export const site = defineSite({
25
+ root: "./content",
26
+ collections: {
27
+ news: collection({
28
+ label: "News",
29
+ labelSingular: "Article",
30
+ fields: { title: text({ required: true }), slug: slug({ from: "title" }) },
31
+ }),
32
+ },
33
+ singletons: {
34
+ settings: singleton({ label: "Website", fields: { siteName: text({ required: true }) } }),
35
+ },
36
+ });
37
+ ```
38
+
39
+ Rules that `defineSite` enforces:
40
+
41
+ - Collection, singleton and field names are lowerCamelCase and start with a
42
+ letter: `news`, `teamMembers`, `publishedOn`. Not `team-members`.
43
+ - A name is a collection or a singleton, not both.
44
+ - Every collection has a `slug()` field. Its value is the name of the entry's
45
+ file or folder, and the entry's address on the website. Use one; with more,
46
+ the first is the slug unless `slugField` names another.
47
+ - A `relation()` names a collection of the same model.
48
+ - A `select()` has at least one option.
49
+
50
+ Collection options: `label` (required), `labelSingular`, `description`,
51
+ `titleField` (the field the editor shows as the entry's title; default
52
+ `title`, else the first `text()` field), `slugField`, `sortable` (editors can
53
+ order the entries by hand) and `defaultSort: { field, direction }` (a field
54
+ name or `"position"`). Without `defaultSort`, the editor lists the entries by
55
+ position: the manual order, or the slugs in alphabetical order before anybody
56
+ ordered them. Singleton options: `label`, `description`, `fields`.
57
+
58
+ Options of every field: `label`, `description`, `required`, `listed` (a column
59
+ in the entry list), `readonly`, `hidden`, `group`, `width` (`"full"`, `"half"`,
60
+ `"third"`).
61
+
62
+ | Field | Stored as | Other options |
63
+ | --- | --- | --- |
64
+ | `text()` | string | `minLength`, `maxLength`, `pattern`, `placeholder`, `default` |
65
+ | `textarea()` | string | `minLength`, `maxLength`, `rows`, `placeholder`, `default` |
66
+ | `richText()` | Markdoc in its own `.mdoc` file | `toolbar`, `maxLength`, `default` |
67
+ | `number()` | number | `min`, `max`, `step`, `integer`, `unit`, `default` |
68
+ | `boolean()` | boolean | `default` |
69
+ | `date()` | `"YYYY-MM-DD"`, or an ISO timestamp with `includeTime: true` | `includeTime`, `min`, `max`, `default` |
70
+ | `email()` | string | `placeholder`, `default` |
71
+ | `url()` | string | `placeholder`, `default` |
72
+ | `slug()` | string | `from` (the field to make it from), `maxLength` |
73
+ | `select()` | string, or string[] with `multiple` | `options: [{ label, value }]` (required), `multiple`, `default` |
74
+ | `object()` | object | `fields` (required), `collapsible`, `collapsed` |
75
+ | `repeater()` | array of objects | `fields` (required), `min`, `max`, `itemLabel`, `addLabel` |
76
+ | `relation()` | the slug of an entry | `collection` (required), `multiple` |
77
+ | `media()` | a file path | `accept: "image" \| "document" \| "any"`, `multiple`, `max` |
78
+ | `color()` | `"#rrggbb"` | `presets`, `default` |
79
+ | `coordinates()` | `{ "lat": 47.37, "lng": 8.54 }` | `defaultCenter`, `defaultZoom` |
80
+
81
+ There is no field for a free list of strings, such as free tags. Use
82
+ `select({ multiple: true, options })` for a fixed list, or a `repeater()` with
83
+ one `text()` field. Ask the user which one.
84
+
85
+ ## The files on disk
86
+
87
+ A collection whose fields include `richText()` or `media()` stores each entry
88
+ as a folder. Any other collection stores each entry as one JSON file.
89
+
90
+ ```
91
+ content/
92
+ ├── .mica/schema.json the model; written by `astro sync`, commit it
93
+ ├── news/ has rich text or media: a folder per entry
94
+ │ ├── _order.json optional: the manual order of a sortable collection
95
+ │ └── hello-world/ the folder name is the slug
96
+ │ ├── index.json all fields except rich text
97
+ │ ├── body.mdoc one file per rich-text field, named after it
98
+ │ └── media/cover.jpg the entry's own files
99
+ ├── team/ plain fields: one file per entry
100
+ │ └── anna-vogt.json the file name is the slug
101
+ └── settings.json a singleton (a folder with index.json if it has rich text or media)
102
+ ```
103
+
104
+ - A slug has lowercase letters, digits and single hyphens: `hello-world`. The
105
+ slug field in the JSON has the same value as the folder or file name.
106
+ - A media path in the JSON is relative to the entry's folder (`media/cover.jpg`),
107
+ or starts with `/` and is relative to the content root (`/shared/logo.png`).
108
+ - A rich-text field in a repeater or an object gets a dotted file name:
109
+ `sections.0.body.mdoc`.
110
+ - An empty field is `null` in the JSON. An empty list is `[]`, an unset
111
+ boolean is `false`, and an `object()` holds its empty fields.
112
+ - Markdoc is Markdown plus `{% tags %}`. Raw HTML is not rendered: Markdoc
113
+ escapes it.
114
+
115
+ ### Write entries with a script
116
+
117
+ To write many entries, use the functions Mica itself uses. Then the files are
118
+ byte for byte what the editor writes. Run `npx astro sync` first, so that the
119
+ model is in `content/.mica/schema.json`. Put the script inside the project, so
120
+ that `@micacms/core` resolves from its `node_modules`.
121
+
122
+ ```js
123
+ // scripts/write-entries.mjs — run with `node scripts/write-entries.mjs`
124
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
125
+ import { dirname, join } from "node:path";
126
+
127
+ import { collectionEntryPath, entryFiles, serializeEntry, singletonPath } from "@micacms/core";
128
+
129
+ const ROOT = "content";
130
+ const model = JSON.parse(await readFile(join(ROOT, ".mica/schema.json"), "utf8"));
131
+
132
+ export async function writeEntry(collection, slug, data) {
133
+ const fields = model.collections[collection].fields;
134
+ const base = collectionEntryPath(fields, collection, slug);
135
+ for (const [path, contents] of Object.entries(entryFiles(fields, base, serializeEntry(fields, data)))) {
136
+ await mkdir(dirname(join(ROOT, path)), { recursive: true });
137
+ await writeFile(join(ROOT, path), contents);
138
+ }
139
+ }
140
+
141
+ // A singleton: `content/<name>.json`, or `content/<name>/index.json` with rich text or media.
142
+ export async function writeSingleton(name, data) {
143
+ const fields = model.singletons[name].fields;
144
+ for (const [path, contents] of Object.entries(entryFiles(fields, singletonPath(fields, name), serializeEntry(fields, data)))) {
145
+ await mkdir(dirname(join(ROOT, path)), { recursive: true });
146
+ await writeFile(join(ROOT, path), contents);
147
+ }
148
+ }
149
+
150
+ // The manual order of a sortable collection. Slugs that it does not name come after.
151
+ export async function writeOrder(collection, slugs) {
152
+ await writeFile(join(ROOT, collection, "_order.json"), `${JSON.stringify(slugs, null, 2)}\n`);
153
+ }
154
+
155
+ // Example: `data` has every field, rich text as a Markdoc string, media as `media/<file>`.
156
+ // await writeEntry("news", "hello-world", {
157
+ // title: "Hello, world",
158
+ // slug: "hello-world",
159
+ // publishedOn: "2026-01-15",
160
+ // cover: "media/cover.jpg",
161
+ // body: "The first article.",
162
+ // });
163
+ ```
164
+
165
+ The script does not copy media files. Copy each file to
166
+ `content/<collection>/<slug>/media/<file>` yourself. Delete the script when the
167
+ content is written, unless the user wants to keep it.
168
+
169
+ ## Render the content in Astro
170
+
171
+ `micaCollections(site)` makes one Astro collection per collection and per
172
+ singleton. Pages use the normal Astro API.
173
+
174
+ ```astro
175
+ ---
176
+ // src/pages/news/[slug].astro
177
+ import { getCollection, getEntry } from "astro:content";
178
+ import { renderRichText } from "@micacms/astro";
179
+
180
+ export async function getStaticPaths() {
181
+ const news = await getCollection("news");
182
+ return news.map((article) => ({ params: { slug: article.id }, props: { article } }));
183
+ }
184
+
185
+ const { article } = Astro.props;
186
+ const settings = await getEntry("settings", "settings");
187
+ ---
188
+ <h1>{article.data.title}</h1>
189
+ <div set:html={renderRichText(article.data.body)} />
190
+ <footer>{settings?.data.siteName}</footer>
191
+ ```
192
+
193
+ - `entry.id` is the slug.
194
+ - A singleton is a collection with one entry. Its id is the singleton's name:
195
+ `getEntry("settings", "settings")`. It is `undefined` until it has content.
196
+ - Rich text is a Markdoc string. `renderRichText()` returns HTML in an
197
+ `<article>` element. There is no `render(entry)` and no `<Content />` for
198
+ Mica collections.
199
+ - A date is a `"YYYY-MM-DD"` string. `new Date()` reads it as UTC midnight,
200
+ so format it in UTC, or it shows the day before west of UTC:
201
+ `new Date(value).toLocaleDateString("en-GB", { timeZone: "UTC" })`.
202
+ - A relation is a slug: `await getEntry("team", article.data.author)`.
203
+ - Do not depend on the order of `getCollection()`. Sort by a date field, or
204
+ by position for a `sortable` collection:
205
+ `entries.sort((a, b) => a.data._mica.position - b.data._mica.position)`.
206
+
207
+ ### Images
208
+
209
+ On disk, a media field holds `media/cover.jpg`. In `entry.data`, the loader
210
+ gives the path from the content root: `news/hello-world/media/cover.jpg`. Astro can only optimize an image that the
211
+ build imports, so collect the images with `import.meta.glob`:
212
+
213
+ ```ts
214
+ // src/lib/media.ts — the glob path is relative to this file
215
+ import type { ImageMetadata } from "astro";
216
+
217
+ const images = import.meta.glob<{ default: ImageMetadata }>(
218
+ "../../content/**/*.{jpeg,jpg,png,webp,avif,gif}",
219
+ { eager: true },
220
+ );
221
+
222
+ /** The image for a Mica media path, or undefined when the file is not there. */
223
+ export function mediaImage(path: string | null | undefined): ImageMetadata | undefined {
224
+ return path ? images[`../../content/${path}`]?.default : undefined;
225
+ }
226
+ ```
227
+
228
+ ```astro
229
+ ---
230
+ import { Image } from "astro:assets";
231
+ import { mediaImage } from "../../lib/media";
232
+ const cover = mediaImage(article.data.cover);
233
+ ---
234
+ {cover && <Image src={cover} alt={article.data.title} />}
235
+ ```
236
+
237
+ A `media({ multiple: true })` field is an array of paths:
238
+ `project.data.gallery.map(mediaImage)`.
239
+
240
+ A media field has no alt text. Add a `text()` field for it where the image
241
+ carries meaning. For documents such as PDF files, use
242
+ `import.meta.glob("../../content/**/*.pdf", { query: "?url", import: "default", eager: true })`
243
+ and link to the URL.
244
+
245
+ ### What Markdoc renders differently from Astro's Markdown
246
+
247
+ - Headings have no `id`, and code blocks are not highlighted. To add them,
248
+ pass Markdoc nodes in `renderRichText(source, { config })`; see
249
+ https://markdoc.dev/docs/nodes. Ask the user first.
250
+ - Raw HTML, MDX components and footnotes are not rendered. `mica check` warns
251
+ about them.
252
+ - A `{% tag %}` renders only if `config.tags` declares it.