@micacms/editor 0.5.0 → 0.6.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 +14 -11
- package/dist/banner.js +1 -1
- package/dist/banner.js.map +1 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +20 -10
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +10 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +112 -30
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -13
- package/dist/config.js.map +1 -1
- package/dist/devalue.d.ts +8 -0
- package/dist/devalue.d.ts.map +1 -0
- package/dist/devalue.js +67 -0
- package/dist/devalue.js.map +1 -0
- package/dist/guards.js +1 -1
- package/dist/guards.js.map +1 -1
- package/dist/import.d.ts +39 -0
- package/dist/import.d.ts.map +1 -0
- package/dist/import.js +268 -0
- package/dist/import.js.map +1 -0
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +18 -2
- package/dist/init.js.map +1 -1
- package/dist/migrate.d.ts +23 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +115 -0
- package/dist/migrate.js.map +1 -0
- package/package.json +13 -6
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Mica Editor
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
3
|
+
> **Public.** From 1.0, semver covers the `mica` CLI (`init`, `check` and the
|
|
4
|
+
> editor's flags). Pre-1.0: it can still change in a minor release.
|
|
5
5
|
|
|
6
6
|
The local Mica editor: `@micacms/runtime` and `@micacms/editor-ui` composed for
|
|
7
7
|
a developer's own machine. No authentication, no user, no database — the
|
|
@@ -22,7 +22,7 @@ localhost:6422
|
|
|
22
22
|
## Running it against a project
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npx @micacms/editor --content ./content
|
|
25
|
+
npx @micacms/editor start --content ./content
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The published package carries the API and the built admin, so this is one
|
|
@@ -44,10 +44,13 @@ keeps Publish out of the editor entirely).
|
|
|
44
44
|
```bash
|
|
45
45
|
npx @micacms/editor init # add Mica to the Astro project in this directory
|
|
46
46
|
npx @micacms/editor check # check every entry against the model
|
|
47
|
+
npx @micacms/editor migrate-layout # move entries after a layout change
|
|
48
|
+
npx @micacms/editor import # move Astro collections into Mica
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
`init` writes a starter `src/mica.ts` and `src/content.config.ts`, and never
|
|
50
|
-
overwrites a file.
|
|
52
|
+
overwrites a file. It also names the `mica` and `check` scripts that
|
|
53
|
+
`package.json` does not have yet. `check` reads every entry the way Mica and the
|
|
51
54
|
website read it, and reports what they would skip or refuse: fields that do
|
|
52
55
|
not validate, missing media, relations to entries that do not exist, slugs
|
|
53
56
|
that do not match their file names, and files that are not read at all. It
|
|
@@ -60,14 +63,12 @@ are also `npx mica init` and `npx mica check`.
|
|
|
60
63
|
rush update
|
|
61
64
|
rush build --to tag:packages
|
|
62
65
|
|
|
63
|
-
cd apps/local-editor
|
|
64
|
-
cp .env.example .env
|
|
65
|
-
|
|
66
66
|
rush dev # API on :6422, admin on :5173
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`
|
|
70
|
-
|
|
69
|
+
`rush dev` reads `dev.env`, which sets
|
|
70
|
+
`MICA_CONTENT_ROOT=../../examples/muster-consulting/content`, so the server edits
|
|
71
|
+
the example project's content. There is nothing to seed: the
|
|
71
72
|
content is already there, in Git.
|
|
72
73
|
|
|
73
74
|
The server has no content model of its own. It reads one from the content
|
|
@@ -79,8 +80,10 @@ no avatar and no account menu, because there is nothing behind them.
|
|
|
79
80
|
|
|
80
81
|
`rush dev` runs this app and the Vite dev server together. Vite
|
|
81
82
|
proxies `/api` to the API; the API still needs
|
|
82
|
-
`
|
|
83
|
-
`.env
|
|
83
|
+
`MICA_ADMIN_ORIGINS=http://localhost:5173` for its Origin check, which
|
|
84
|
+
`dev.env` sets. The published editor reads no `.env` file: the project's `.env`
|
|
85
|
+
belongs to the site. Every variable is at
|
|
86
|
+
<https://micacms.com/docs/configuration/>.
|
|
84
87
|
|
|
85
88
|
## What lives where
|
|
86
89
|
|
package/dist/banner.js
CHANGED
|
@@ -83,6 +83,6 @@ export function portInUse(port, host, useColor) {
|
|
|
83
83
|
return (`\n ${c.bold(`Port ${port} is already in use on ${host}.`)}\n\n` +
|
|
84
84
|
` Something else is listening there — another Mica, most likely.\n` +
|
|
85
85
|
` Stop it, or give this one a different port:\n\n` +
|
|
86
|
-
` npx @micacms/editor --port ${port + 1}\n\n`);
|
|
86
|
+
` npx @micacms/editor start --port ${port + 1}\n\n`);
|
|
87
87
|
}
|
|
88
88
|
//# sourceMappingURL=banner.js.map
|
package/dist/banner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"banner.js","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"banner.js","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `npx @micacms/editor` says when it works.\n *\n * The startup log lines are written for whoever is reading a container's\n * output later: every field that could matter, flat, greppable, and in no\n * particular order of importance. A person who has just typed a command needs\n * the opposite — the address to open, the directory being edited, and what\n * pressing Save will do to their repository. Three facts, in that order,\n * because the first minute decides whether there is a second one.\n *\n * Everything here is decoration over information the server already has. It is\n * printed instead of the startup log lines, never as well: two accounts of the\n * same event, disagreeing in tone, is worse than either alone.\n */\n\nimport path from \"node:path\";\n\nexport interface Banner {\n version: string;\n /** The origin the person should open, without the `/admin/` path. */\n url: string;\n contentRoot: string;\n collections: number;\n singletons: number;\n}\n\n/** ANSI, or nothing at all when the output is not a terminal. */\nexport function colors(enabled: boolean) {\n const wrap = (code: string) => (text: string) => (enabled ? `\u001b[${code}m${text}\u001b[0m` : text);\n return {\n bold: wrap(\"1\"),\n dim: wrap(\"2\"),\n brand: wrap(\"36\"),\n label: wrap(\"2\"),\n };\n}\n\n/**\n * True unless something says otherwise.\n *\n * `NO_COLOR` is the convention (no-color.org); a pipe or a file is the other\n * case, and escape codes in a redirected log help nobody.\n */\nexport function supportsColor(stream: { isTTY?: boolean }, env: NodeJS.ProcessEnv): boolean {\n if (env[\"NO_COLOR\"]) return false;\n if (env[\"FORCE_COLOR\"]) return true;\n return Boolean(stream.isTTY);\n}\n\n/** Paths inside the working directory read better as the relative ones. */\nfunction readable(target: string, cwd = process.cwd()): string {\n const relative = path.relative(cwd, target);\n if (!relative || relative.startsWith(\"..\") || path.isAbsolute(relative)) return target;\n return `./${relative}`;\n}\n\n/** \"3 collections and 2 singletons\", or whichever half is not zero. */\nfunction modelSummary(collections: number, singletons: number): string {\n const plural = (count: number, word: string) => `${count} ${word}${count === 1 ? \"\" : \"s\"}`;\n const parts: string[] = [];\n if (collections > 0) parts.push(plural(collections, \"collection\"));\n if (singletons > 0) parts.push(plural(singletons, \"singleton\"));\n return parts.join(\" and \") || \"an empty model\";\n}\n\nexport function startupBanner(banner: Banner, useColor: boolean): string {\n const c = colors(useColor);\n const editor = `${banner.url}/admin/`;\n\n const row = (label: string, value: string): string =>\n ` ${c.label(label.padEnd(9))} ${value}`;\n\n const lines = [\n \"\",\n ` ${c.brand(\"◆\")} ${c.bold(\"Mica\")} ${c.dim(banner.version)}`,\n \"\",\n row(\"Editor\", c.bold(editor)),\n row(\"Content\", `${readable(banner.contentRoot)} ${c.dim(`— ${modelSummary(banner.collections, banner.singletons)}`)}`),\n row(\"Saving\", c.dim(\"writes files into that directory; Publish commits and pushes them.\")),\n \"\",\n ` ${c.dim(\"No sign-in on your own machine — the link above opens straight into the editor.\")}`,\n ` ${c.dim(\"Mica keeps no state of its own here: everything you save is a file in that directory.\")}`,\n \"\",\n ` ${c.dim(\"Run your site's dev server beside this to see the changes. Ctrl+C stops Mica.\")}`,\n \"\",\n ];\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\n/**\n * The one failure this command is most likely to hit, and the one the operating\n * system explains worst: `EADDRINUSE` alone tells nobody which port or what to\n * do instead.\n */\nexport function portInUse(port: number, host: string, useColor: boolean): string {\n const c = colors(useColor);\n return (\n `\\n ${c.bold(`Port ${port} is already in use on ${host}.`)}\\n\\n` +\n ` Something else is listening there — another Mica, most likely.\\n` +\n ` Stop it, or give this one a different port:\\n\\n` +\n ` npx @micacms/editor start --port ${port + 1}\\n\\n`\n );\n}\n"]}
|
package/dist/check.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4BH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,uFAAuF;AACvF,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0ErE;AAsJD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAexD;AAuED,mDAAmD;AACnD,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBtE"}
|
package/dist/check.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { relative } from "node:path";
|
|
8
|
-
import { DOCUMENT_EXTENSION, documentPath, entryLayout, INDEX_FILE, isValidSlug, MANIFEST_PATH, ORDER_FILE, readCollectionEntry, readSingleton, resolveSlugField, singletonPath, validateFields, } from "@micacms/core";
|
|
8
|
+
import { DOCUMENT_EXTENSION, documentPath, entryLayout, INDEX_FILE, isValidSlug, MANIFEST_PATH, misplacedFiles, ORDER_FILE, readCollectionEntry, readSingleton, resolveSlugField, singletonPath, validateFields, } from "@micacms/core";
|
|
9
9
|
import { within } from "@micacms/core/node";
|
|
10
10
|
import { contentFiles, loadSchema, readOnlyParts } from "@micacms/runtime";
|
|
11
11
|
/** Checks the content under `root` against the model in `<root>/.mica/schema.json`. */
|
|
@@ -64,9 +64,12 @@ export async function checkContent(root) {
|
|
|
64
64
|
const directory = entryLayout(def.fields) === "directory";
|
|
65
65
|
const base = singletonPath(def.fields, name);
|
|
66
66
|
const dataPath = directory ? `${base}/${INDEX_FILE}` : base;
|
|
67
|
-
const otherLayout =
|
|
68
|
-
if (
|
|
69
|
-
error(otherLayout, `Not read: "${def.label}" is stored as ${dataPath}, because ${directory ? "it has" : "it has no"} rich text or media
|
|
67
|
+
const [otherLayout] = await misplacedFiles(files, def.fields, name, "singleton");
|
|
68
|
+
if (otherLayout) {
|
|
69
|
+
error(otherLayout, `Not read: "${def.label}" is stored as ${dataPath}, because ${directory ? "it has" : "it has no"} rich text or media. Run \`npx mica migrate-layout\`.`);
|
|
70
|
+
// Without a file in the model's layout, the reader stops at the other one: reported above.
|
|
71
|
+
if ((await readText(files, dataPath)) === null)
|
|
72
|
+
continue;
|
|
70
73
|
}
|
|
71
74
|
const data = await readOrReport(() => readSingleton(files, def, name), dataPath, error);
|
|
72
75
|
if (data === undefined)
|
|
@@ -84,6 +87,8 @@ async function entrySlugs(files, fields, name, error, warning) {
|
|
|
84
87
|
const directory = entryLayout(fields) === "directory";
|
|
85
88
|
const shape = directory ? `a folder with ${INDEX_FILE}` : "a <slug>.json file";
|
|
86
89
|
const found = [];
|
|
90
|
+
// The reader's own rule, so that `check` and `migrate-layout` agree.
|
|
91
|
+
const misplaced = new Set(await misplacedFiles(files, fields, name, "collection"));
|
|
87
92
|
for (const item of await files.list(name)) {
|
|
88
93
|
const path = `${name}/${item.name}`;
|
|
89
94
|
if (item.name.startsWith("."))
|
|
@@ -93,12 +98,12 @@ async function entrySlugs(files, fields, name, error, warning) {
|
|
|
93
98
|
continue;
|
|
94
99
|
}
|
|
95
100
|
// An entry in the other layout is lost content, not a stray file: the model gained or lost rich text or media.
|
|
96
|
-
if (directory &&
|
|
97
|
-
error(path, `Not read: entries of "${name}" are folders with ${INDEX_FILE}, because it has rich text or media
|
|
101
|
+
if (directory && misplaced.has(path)) {
|
|
102
|
+
error(path, `Not read: entries of "${name}" are folders with ${INDEX_FILE}, because it has rich text or media. Run \`npx mica migrate-layout\`.`);
|
|
98
103
|
continue;
|
|
99
104
|
}
|
|
100
|
-
if (!directory &&
|
|
101
|
-
error(path, `Not read: entries of "${name}" are <slug>.json files, because it has no rich text or media
|
|
105
|
+
if (!directory && misplaced.has(`${path}/${INDEX_FILE}`)) {
|
|
106
|
+
error(path, `Not read: entries of "${name}" are <slug>.json files, because it has no rich text or media. Run \`npx mica migrate-layout\`.`);
|
|
102
107
|
continue;
|
|
103
108
|
}
|
|
104
109
|
const slug = directory ? item.name : item.name.replace(/\.json$/, "");
|
|
@@ -156,7 +161,7 @@ async function readOrReport(read, path, error) {
|
|
|
156
161
|
}
|
|
157
162
|
async function checkEntry(context, fields, data, dataPath, base) {
|
|
158
163
|
const { root, files, slugs, error, warning } = context;
|
|
159
|
-
const result = validateFields(fields, data
|
|
164
|
+
const result = validateFields(fields, data);
|
|
160
165
|
if (!result.ok) {
|
|
161
166
|
for (const issue of result.issues)
|
|
162
167
|
error(dataPath, issue.path ? `${issue.path}: ${issue.message}` : issue.message);
|
|
@@ -167,7 +172,7 @@ async function checkEntry(context, fields, data, dataPath, base) {
|
|
|
167
172
|
visit(fields, data, [], (field, value, path) => {
|
|
168
173
|
const key = path.join(".");
|
|
169
174
|
if (field.kind === "media") {
|
|
170
|
-
for (const file of list(value)) {
|
|
175
|
+
for (const file of list(mediaPaths(value))) {
|
|
171
176
|
let exists = false;
|
|
172
177
|
try {
|
|
173
178
|
exists = existsSync(within(root, file));
|
|
@@ -220,6 +225,11 @@ export function markdocWarnings(source) {
|
|
|
220
225
|
messages.push("Footnotes ([^1]) are not Markdoc: they render as plain text.");
|
|
221
226
|
return messages;
|
|
222
227
|
}
|
|
228
|
+
/** The paths of a media value: a path, `{ path, alt }`, or a list of either. */
|
|
229
|
+
function mediaPaths(value) {
|
|
230
|
+
const values = Array.isArray(value) ? value : [value];
|
|
231
|
+
return values.map((item) => (item !== null && typeof item === "object" ? item.path : item));
|
|
232
|
+
}
|
|
223
233
|
function list(value) {
|
|
224
234
|
const values = Array.isArray(value) ? value : [value];
|
|
225
235
|
return values.filter((item) => typeof item === "string" && item !== "");
|
package/dist/check.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `mica check` — reads every entry the way Mica and the website read it, and\n * reports what they would skip, refuse or render as a gap. It never opens a\n * browser and exits with 1 when it finds an error, for CI and coding agents.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\n\nimport {\n DOCUMENT_EXTENSION,\n documentPath,\n entryLayout,\n INDEX_FILE,\n isValidSlug,\n MANIFEST_PATH,\n ORDER_FILE,\n readCollectionEntry,\n readSingleton,\n resolveSlugField,\n singletonPath,\n validateFields,\n type AnyField,\n type ContentSource,\n type FieldMap,\n type PathSegment,\n type Site,\n} from \"@micacms/core\";\nimport { within } from \"@micacms/core/node\";\nimport { contentFiles, loadSchema, readOnlyParts } from \"@micacms/runtime\";\n\nexport interface CheckProblem {\n level: \"error\" | \"warning\";\n /** The file, relative to the content root. */\n path: string;\n message: string;\n}\n\nexport interface CheckReport {\n problems: CheckProblem[];\n /** False when the model could not be read, so nothing else was checked. */\n model: boolean;\n collections: number;\n entries: number;\n singletons: number;\n}\n\n/** Checks the content under `root` against the model in `<root>/.mica/schema.json`. */\nexport async function checkContent(root: string): Promise<CheckReport> {\n const files = contentFiles({ root });\n const report: CheckReport = { problems: [], model: false, collections: 0, entries: 0, singletons: 0 };\n const error = (path: string, message: string) => report.problems.push({ level: \"error\", path, message });\n const warning = (path: string, message: string) => report.problems.push({ level: \"warning\", path, message });\n\n const manifest = await readText(files, MANIFEST_PATH);\n if (manifest === null || manifest instanceof Error) {\n error(MANIFEST_PATH, manifest?.message ?? \"There is no content model. Run `npx astro sync` in the Astro project to write it.\");\n return report;\n }\n let site: Site;\n try {\n site = await loadSchema(files);\n } catch (cause) {\n error(MANIFEST_PATH, (cause as Error).message);\n return report;\n }\n report.model = true;\n\n // A model from a newer @micacms/astro: this check cannot judge those entries.\n const readOnly = readOnlyParts(site);\n for (const reason of readOnly.values()) error(MANIFEST_PATH, `Not checked. ${reason}`);\n\n const slugs = new Map<string, Set<string>>();\n for (const [name, def] of Object.entries(site.collections)) {\n slugs.set(name, new Set(await entrySlugs(files, def.fields, name, error, warning)));\n }\n\n const context: EntryContext = { root, files, site, slugs, error, warning };\n\n for (const [name, def] of Object.entries(site.collections)) {\n report.collections++;\n if (readOnly.has(`collections.${name}`)) continue;\n const slugField = resolveSlugField(def);\n for (const slug of slugs.get(name)!) {\n report.entries++;\n const directory = entryLayout(def.fields) === \"directory\";\n const base = directory ? `${name}/${slug}` : null;\n const dataPath = directory ? `${base}/${INDEX_FILE}` : `${name}/${slug}.json`;\n\n const data = await readOrReport(() => readCollectionEntry(files, def, name, slug), dataPath, error);\n if (!data) continue;\n const stored = slugField ? data[slugField] : undefined;\n if (typeof stored === \"string\" && stored !== \"\" && stored !== slug) {\n warning(dataPath, `${slugField} is \"${stored}\", but the entry is named \"${slug}\". The next save in the editor renames the entry.`);\n }\n await checkEntry(context, def.fields, data, dataPath, base);\n }\n }\n\n for (const [name, def] of Object.entries(site.singletons)) {\n report.singletons++;\n if (readOnly.has(`singletons.${name}`)) continue;\n const directory = entryLayout(def.fields) === \"directory\";\n const base = singletonPath(def.fields, name);\n const dataPath = directory ? `${base}/${INDEX_FILE}` : base;\n const otherLayout = directory ? `${name}.json` : `${name}/${INDEX_FILE}`;\n if ((await readText(files, otherLayout)) !== null) {\n error(otherLayout, `Not read: \"${def.label}\" is stored as ${dataPath}, because ${directory ? \"it has\" : \"it has no\"} rich text or media.`);\n }\n\n const data = await readOrReport(() => readSingleton(files, def, name), dataPath, error);\n if (data === undefined) continue;\n if (data === null) {\n warning(dataPath, `No content for \"${def.label}\" yet.`);\n continue;\n }\n await checkEntry(context, def.fields, data, dataPath, directory ? base : null);\n }\n\n return report;\n}\n\ninterface EntryContext {\n root: string;\n files: ContentSource;\n site: Site;\n slugs: Map<string, Set<string>>;\n error(path: string, message: string): void;\n warning(path: string, message: string): void;\n}\n\n/** The slugs Mica reads in a collection, reporting what it would skip. */\nasync function entrySlugs(\n files: ContentSource,\n fields: FieldMap,\n name: string,\n error: (path: string, message: string) => void,\n warning: (path: string, message: string) => void,\n): Promise<string[]> {\n const directory = entryLayout(fields) === \"directory\";\n const shape = directory ? `a folder with ${INDEX_FILE}` : \"a <slug>.json file\";\n const found: string[] = [];\n\n for (const item of await files.list(name)) {\n const path = `${name}/${item.name}`;\n if (item.name.startsWith(\".\")) continue;\n if (item.name === ORDER_FILE) {\n checkOrder(await readText(files, path), path, error);\n continue;\n }\n\n // An entry in the other layout is lost content, not a stray file: the model gained or lost rich text or media.\n if (directory && item.type === \"file\" && item.name.endsWith(\".json\")) {\n error(path, `Not read: entries of \"${name}\" are folders with ${INDEX_FILE}, because it has rich text or media.`);\n continue;\n }\n if (!directory && item.type === \"directory\" && (await readText(files, `${path}/${INDEX_FILE}`)) !== null) {\n error(path, `Not read: entries of \"${name}\" are <slug>.json files, because it has no rich text or media.`);\n continue;\n }\n\n const slug = directory ? item.name : item.name.replace(/\\.json$/, \"\");\n const looksLikeEntry = directory ? item.type === \"directory\" : item.type === \"file\" && item.name.endsWith(\".json\");\n if (!looksLikeEntry) {\n warning(path, `Not read. Each entry of \"${name}\" is ${shape}.`);\n continue;\n }\n if (!isValidSlug(slug)) {\n error(path, `Not read: \"${slug}\" is not a slug. Use lowercase letters, digits and single hyphens.`);\n continue;\n }\n const index = directory ? await readText(files, `${path}/${INDEX_FILE}`) : \"\";\n if (index === null || index instanceof Error) {\n error(path, `Not read: ${index?.message ?? `the folder has no ${INDEX_FILE}.`}`);\n continue;\n }\n found.push(slug);\n }\n return found;\n}\n\n/** The file's text, null when it is not there, or the error that reading it gave. */\nasync function readText(files: ContentSource, path: string): Promise<string | null | Error> {\n try {\n return await files.read(path);\n } catch (cause) {\n return cause as Error;\n }\n}\n\nfunction checkOrder(text: string | null | Error, path: string, error: (path: string, message: string) => void): void {\n if (text === null) return;\n if (text instanceof Error) return error(path, text.message);\n let order: unknown;\n try {\n order = JSON.parse(text);\n } catch {\n return error(path, \"Not valid JSON. The build fails on it.\");\n }\n if (!Array.isArray(order)) {\n error(path, 'Must be a JSON array of slugs, such as [\"second\", \"first\"]. The build fails on it.');\n }\n}\n\nasync function readOrReport<T>(\n read: () => Promise<T>,\n path: string,\n error: (path: string, message: string) => void,\n): Promise<T | undefined> {\n try {\n return await read();\n } catch (cause) {\n error(path, (cause as Error).message.replace(`${path}: `, \"\"));\n return undefined;\n }\n}\n\nasync function checkEntry(\n context: EntryContext,\n fields: FieldMap,\n data: Record<string, unknown>,\n dataPath: string,\n base: string | null,\n): Promise<void> {\n const { root, files, slugs, error, warning } = context;\n\n const result = validateFields(fields, data, \"storage\");\n if (!result.ok) {\n for (const issue of result.issues) error(dataPath, issue.path ? `${issue.path}: ${issue.message}` : issue.message);\n }\n\n const raw = JSON.parse((await files.read(dataPath))!) as Record<string, unknown>;\n for (const message of unknownKeys(fields, raw, [])) warning(dataPath, message);\n\n visit(fields, data, [], (field, value, path) => {\n const key = path.join(\".\");\n if (field.kind === \"media\") {\n for (const file of list(value)) {\n let exists = false;\n try {\n exists = existsSync(within(root, file));\n } catch {\n // A path outside the content root does not exist for Mica.\n }\n if (!exists) error(dataPath, `${key}: the file \"${file}\" does not exist under the content root.`);\n }\n }\n if (field.kind === \"relation\") {\n for (const slug of list(value)) {\n if (!slugs.get(field.collection)?.has(slug)) {\n error(dataPath, `${key}: there is no \"${slug}\" in \"${field.collection}\".`);\n }\n }\n }\n });\n\n if (base === null) return;\n for (const item of await files.list(base)) {\n if (item.type !== \"file\" || !item.name.endsWith(DOCUMENT_EXTENSION)) continue;\n const path = `${base}/${item.name}`;\n if (!namesRichText(fields, documentPath(item.name))) {\n warning(path, \"Not read: no rich-text field has this name.\");\n continue;\n }\n for (const message of markdocWarnings((await files.read(path)) ?? \"\")) warning(path, message);\n }\n}\n\n/**\n * What Markdoc renders differently from Markdown, found in the text outside\n * code. A scan, not a parse: it can warn about too much, never about too little.\n */\nexport function markdocWarnings(source: string): string[] {\n const prose = source.replace(/^(`{3,}|~{3,})[^\\n]*\\n[\\s\\S]*?^\\1[ \\t]*$/gm, \"\").replace(/`[^`\\n]*`/g, \"\");\n const messages: string[] = [];\n const html = [...new Set([...prose.matchAll(/<\\/?([a-zA-Z][a-zA-Z0-9-]*)(?=[\\s/>])[^>\\n]*>/g)].map((match) => match[1]!.toLowerCase()))];\n if (html.length > 0) {\n messages.push(`Raw HTML (${html.map((tag) => `<${tag}>`).join(\", \")}) is not rendered: Markdoc escapes it.`);\n }\n const tags = [...new Set([...prose.matchAll(/\\{%\\s*([a-zA-Z][\\w-]*)/g)].map((match) => match[1]!))];\n if (tags.length > 0) {\n messages.push(\n `Markdoc tags (${tags.join(\", \")}) render only if the website declares them in renderRichText(source, { config }).`,\n );\n }\n if (/\\[\\^[^\\]]+\\]/.test(prose)) messages.push(\"Footnotes ([^1]) are not Markdoc: they render as plain text.\");\n return messages;\n}\n\nfunction list(value: unknown): string[] {\n const values = Array.isArray(value) ? value : [value];\n return values.filter((item): item is string => typeof item === \"string\" && item !== \"\");\n}\n\n/** Calls `onField` for every field with a value, nested fields included. */\nfunction visit(\n fields: FieldMap,\n data: Record<string, unknown>,\n path: PathSegment[],\n onField: (field: AnyField, value: unknown, path: PathSegment[]) => void,\n): void {\n for (const [key, field] of Object.entries(fields)) {\n const value = data[key];\n if (value === undefined || value === null) continue;\n onField(field, value, [...path, key]);\n if (field.kind === \"object\" && isRecord(value)) visit(field.fields, value, [...path, key], onField);\n if (field.kind === \"repeater\" && Array.isArray(value)) {\n value.forEach((row, index) => {\n if (isRecord(row)) visit(field.fields, row, [...path, key, index], onField);\n });\n }\n }\n}\n\n/** Keys in the stored JSON that the model does not have; Mica drops them on the next save. */\nfunction unknownKeys(fields: FieldMap, raw: Record<string, unknown>, path: PathSegment[]): string[] {\n const messages: string[] = [];\n for (const [key, value] of Object.entries(raw)) {\n const at = [...path, key].join(\".\");\n const field = fields[key];\n if (!field) {\n messages.push(`${at}: the model has no such field. Mica drops it on the next save.`);\n } else if (field.kind === \"richText\") {\n messages.push(`${at}: rich text belongs in ${[...path, key].join(\".\")}${DOCUMENT_EXTENSION}, not in the JSON.`);\n } else if (field.kind === \"object\" && isRecord(value)) {\n messages.push(...unknownKeys(field.fields, value, [...path, key]));\n } else if (field.kind === \"repeater\" && Array.isArray(value)) {\n value.forEach((row, index) => {\n if (isRecord(row)) messages.push(...unknownKeys(field.fields, row, [...path, key, index]));\n });\n }\n }\n return messages;\n}\n\n/** Whether a sidecar's dotted path leads to a rich-text field. */\nfunction namesRichText(fields: FieldMap, path: PathSegment[]): boolean {\n let map: FieldMap | undefined = fields;\n for (const [index, segment] of path.entries()) {\n if (typeof segment === \"number\") continue;\n const field: AnyField | undefined = map?.[segment];\n if (!field) return false;\n if (index === path.length - 1) return field.kind === \"richText\";\n map = field.kind === \"object\" || field.kind === \"repeater\" ? field.fields : undefined;\n }\n return false;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** The report as a person or an agent reads it. */\nexport function formatReport(report: CheckReport, root: string): string {\n const errors = report.problems.filter((problem) => problem.level === \"error\").length;\n const warnings = report.problems.length - errors;\n const lines = [\n report.model\n ? `Checked ${report.entries} ${report.entries === 1 ? \"entry\" : \"entries\"} in ${report.collections} ` +\n `${report.collections === 1 ? \"collection\" : \"collections\"} and ${report.singletons} ` +\n `${report.singletons === 1 ? \"singleton\" : \"singletons\"} in ${relative(process.cwd(), root) || \".\"}.`\n : `Could not read the content model in ${relative(process.cwd(), root) || \".\"}, so no entry was checked.`,\n \"\",\n ];\n for (const problem of report.problems) {\n lines.push(` ${problem.level === \"error\" ? \"error \" : \"warning\"} ${problem.path}`);\n for (const line of problem.message.split(\"\\n\")) lines.push(` ${line.trim()}`);\n }\n if (report.problems.length > 0) lines.push(\"\");\n lines.push(`${errors} ${errors === 1 ? \"error\" : \"errors\"}, ${warnings} ${warnings === 1 ? \"warning\" : \"warnings\"}.`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `mica check` — reads every entry the way Mica and the website read it, and\n * reports what they would skip, refuse or render as a gap. It never opens a\n * browser and exits with 1 when it finds an error, for CI and coding agents.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\n\nimport {\n DOCUMENT_EXTENSION,\n documentPath,\n entryLayout,\n INDEX_FILE,\n isValidSlug,\n MANIFEST_PATH,\n misplacedFiles,\n ORDER_FILE,\n readCollectionEntry,\n readSingleton,\n resolveSlugField,\n singletonPath,\n validateFields,\n type AnyField,\n type ContentSource,\n type FieldMap,\n type PathSegment,\n type Site,\n} from \"@micacms/core\";\nimport { within } from \"@micacms/core/node\";\nimport { contentFiles, loadSchema, readOnlyParts } from \"@micacms/runtime\";\n\nexport interface CheckProblem {\n level: \"error\" | \"warning\";\n /** The file, relative to the content root. */\n path: string;\n message: string;\n}\n\nexport interface CheckReport {\n problems: CheckProblem[];\n /** False when the model could not be read, so nothing else was checked. */\n model: boolean;\n collections: number;\n entries: number;\n singletons: number;\n}\n\n/** Checks the content under `root` against the model in `<root>/.mica/schema.json`. */\nexport async function checkContent(root: string): Promise<CheckReport> {\n const files = contentFiles({ root });\n const report: CheckReport = { problems: [], model: false, collections: 0, entries: 0, singletons: 0 };\n const error = (path: string, message: string) => report.problems.push({ level: \"error\", path, message });\n const warning = (path: string, message: string) => report.problems.push({ level: \"warning\", path, message });\n\n const manifest = await readText(files, MANIFEST_PATH);\n if (manifest === null || manifest instanceof Error) {\n error(MANIFEST_PATH, manifest?.message ?? \"There is no content model. Run `npx astro sync` in the Astro project to write it.\");\n return report;\n }\n let site: Site;\n try {\n site = await loadSchema(files);\n } catch (cause) {\n error(MANIFEST_PATH, (cause as Error).message);\n return report;\n }\n report.model = true;\n\n // A model from a newer @micacms/astro: this check cannot judge those entries.\n const readOnly = readOnlyParts(site);\n for (const reason of readOnly.values()) error(MANIFEST_PATH, `Not checked. ${reason}`);\n\n const slugs = new Map<string, Set<string>>();\n for (const [name, def] of Object.entries(site.collections)) {\n slugs.set(name, new Set(await entrySlugs(files, def.fields, name, error, warning)));\n }\n\n const context: EntryContext = { root, files, site, slugs, error, warning };\n\n for (const [name, def] of Object.entries(site.collections)) {\n report.collections++;\n if (readOnly.has(`collections.${name}`)) continue;\n const slugField = resolveSlugField(def);\n for (const slug of slugs.get(name)!) {\n report.entries++;\n const directory = entryLayout(def.fields) === \"directory\";\n const base = directory ? `${name}/${slug}` : null;\n const dataPath = directory ? `${base}/${INDEX_FILE}` : `${name}/${slug}.json`;\n\n const data = await readOrReport(() => readCollectionEntry(files, def, name, slug), dataPath, error);\n if (!data) continue;\n const stored = slugField ? data[slugField] : undefined;\n if (typeof stored === \"string\" && stored !== \"\" && stored !== slug) {\n warning(dataPath, `${slugField} is \"${stored}\", but the entry is named \"${slug}\". The next save in the editor renames the entry.`);\n }\n await checkEntry(context, def.fields, data, dataPath, base);\n }\n }\n\n for (const [name, def] of Object.entries(site.singletons)) {\n report.singletons++;\n if (readOnly.has(`singletons.${name}`)) continue;\n const directory = entryLayout(def.fields) === \"directory\";\n const base = singletonPath(def.fields, name);\n const dataPath = directory ? `${base}/${INDEX_FILE}` : base;\n const [otherLayout] = await misplacedFiles(files, def.fields, name, \"singleton\");\n if (otherLayout) {\n error(otherLayout, `Not read: \"${def.label}\" is stored as ${dataPath}, because ${directory ? \"it has\" : \"it has no\"} rich text or media. Run \\`npx mica migrate-layout\\`.`);\n // Without a file in the model's layout, the reader stops at the other one: reported above.\n if ((await readText(files, dataPath)) === null) continue;\n }\n\n const data = await readOrReport(() => readSingleton(files, def, name), dataPath, error);\n if (data === undefined) continue;\n if (data === null) {\n warning(dataPath, `No content for \"${def.label}\" yet.`);\n continue;\n }\n await checkEntry(context, def.fields, data, dataPath, directory ? base : null);\n }\n\n return report;\n}\n\ninterface EntryContext {\n root: string;\n files: ContentSource;\n site: Site;\n slugs: Map<string, Set<string>>;\n error(path: string, message: string): void;\n warning(path: string, message: string): void;\n}\n\n/** The slugs Mica reads in a collection, reporting what it would skip. */\nasync function entrySlugs(\n files: ContentSource,\n fields: FieldMap,\n name: string,\n error: (path: string, message: string) => void,\n warning: (path: string, message: string) => void,\n): Promise<string[]> {\n const directory = entryLayout(fields) === \"directory\";\n const shape = directory ? `a folder with ${INDEX_FILE}` : \"a <slug>.json file\";\n const found: string[] = [];\n // The reader's own rule, so that `check` and `migrate-layout` agree.\n const misplaced = new Set(await misplacedFiles(files, fields, name, \"collection\"));\n\n for (const item of await files.list(name)) {\n const path = `${name}/${item.name}`;\n if (item.name.startsWith(\".\")) continue;\n if (item.name === ORDER_FILE) {\n checkOrder(await readText(files, path), path, error);\n continue;\n }\n\n // An entry in the other layout is lost content, not a stray file: the model gained or lost rich text or media.\n if (directory && misplaced.has(path)) {\n error(path, `Not read: entries of \"${name}\" are folders with ${INDEX_FILE}, because it has rich text or media. Run \\`npx mica migrate-layout\\`.`);\n continue;\n }\n if (!directory && misplaced.has(`${path}/${INDEX_FILE}`)) {\n error(path, `Not read: entries of \"${name}\" are <slug>.json files, because it has no rich text or media. Run \\`npx mica migrate-layout\\`.`);\n continue;\n }\n\n const slug = directory ? item.name : item.name.replace(/\\.json$/, \"\");\n const looksLikeEntry = directory ? item.type === \"directory\" : item.type === \"file\" && item.name.endsWith(\".json\");\n if (!looksLikeEntry) {\n warning(path, `Not read. Each entry of \"${name}\" is ${shape}.`);\n continue;\n }\n if (!isValidSlug(slug)) {\n error(path, `Not read: \"${slug}\" is not a slug. Use lowercase letters, digits and single hyphens.`);\n continue;\n }\n const index = directory ? await readText(files, `${path}/${INDEX_FILE}`) : \"\";\n if (index === null || index instanceof Error) {\n error(path, `Not read: ${index?.message ?? `the folder has no ${INDEX_FILE}.`}`);\n continue;\n }\n found.push(slug);\n }\n return found;\n}\n\n/** The file's text, null when it is not there, or the error that reading it gave. */\nasync function readText(files: ContentSource, path: string): Promise<string | null | Error> {\n try {\n return await files.read(path);\n } catch (cause) {\n return cause as Error;\n }\n}\n\nfunction checkOrder(text: string | null | Error, path: string, error: (path: string, message: string) => void): void {\n if (text === null) return;\n if (text instanceof Error) return error(path, text.message);\n let order: unknown;\n try {\n order = JSON.parse(text);\n } catch {\n return error(path, \"Not valid JSON. The build fails on it.\");\n }\n if (!Array.isArray(order)) {\n error(path, 'Must be a JSON array of slugs, such as [\"second\", \"first\"]. The build fails on it.');\n }\n}\n\nasync function readOrReport<T>(\n read: () => Promise<T>,\n path: string,\n error: (path: string, message: string) => void,\n): Promise<T | undefined> {\n try {\n return await read();\n } catch (cause) {\n error(path, (cause as Error).message.replace(`${path}: `, \"\"));\n return undefined;\n }\n}\n\nasync function checkEntry(\n context: EntryContext,\n fields: FieldMap,\n data: Record<string, unknown>,\n dataPath: string,\n base: string | null,\n): Promise<void> {\n const { root, files, slugs, error, warning } = context;\n\n const result = validateFields(fields, data);\n if (!result.ok) {\n for (const issue of result.issues) error(dataPath, issue.path ? `${issue.path}: ${issue.message}` : issue.message);\n }\n\n const raw = JSON.parse((await files.read(dataPath))!) as Record<string, unknown>;\n for (const message of unknownKeys(fields, raw, [])) warning(dataPath, message);\n\n visit(fields, data, [], (field, value, path) => {\n const key = path.join(\".\");\n if (field.kind === \"media\") {\n for (const file of list(mediaPaths(value))) {\n let exists = false;\n try {\n exists = existsSync(within(root, file));\n } catch {\n // A path outside the content root does not exist for Mica.\n }\n if (!exists) error(dataPath, `${key}: the file \"${file}\" does not exist under the content root.`);\n }\n }\n if (field.kind === \"relation\") {\n for (const slug of list(value)) {\n if (!slugs.get(field.collection)?.has(slug)) {\n error(dataPath, `${key}: there is no \"${slug}\" in \"${field.collection}\".`);\n }\n }\n }\n });\n\n if (base === null) return;\n for (const item of await files.list(base)) {\n if (item.type !== \"file\" || !item.name.endsWith(DOCUMENT_EXTENSION)) continue;\n const path = `${base}/${item.name}`;\n if (!namesRichText(fields, documentPath(item.name))) {\n warning(path, \"Not read: no rich-text field has this name.\");\n continue;\n }\n for (const message of markdocWarnings((await files.read(path)) ?? \"\")) warning(path, message);\n }\n}\n\n/**\n * What Markdoc renders differently from Markdown, found in the text outside\n * code. A scan, not a parse: it can warn about too much, never about too little.\n */\nexport function markdocWarnings(source: string): string[] {\n const prose = source.replace(/^(`{3,}|~{3,})[^\\n]*\\n[\\s\\S]*?^\\1[ \\t]*$/gm, \"\").replace(/`[^`\\n]*`/g, \"\");\n const messages: string[] = [];\n const html = [...new Set([...prose.matchAll(/<\\/?([a-zA-Z][a-zA-Z0-9-]*)(?=[\\s/>])[^>\\n]*>/g)].map((match) => match[1]!.toLowerCase()))];\n if (html.length > 0) {\n messages.push(`Raw HTML (${html.map((tag) => `<${tag}>`).join(\", \")}) is not rendered: Markdoc escapes it.`);\n }\n const tags = [...new Set([...prose.matchAll(/\\{%\\s*([a-zA-Z][\\w-]*)/g)].map((match) => match[1]!))];\n if (tags.length > 0) {\n messages.push(\n `Markdoc tags (${tags.join(\", \")}) render only if the website declares them in renderRichText(source, { config }).`,\n );\n }\n if (/\\[\\^[^\\]]+\\]/.test(prose)) messages.push(\"Footnotes ([^1]) are not Markdoc: they render as plain text.\");\n return messages;\n}\n\n/** The paths of a media value: a path, `{ path, alt }`, or a list of either. */\nfunction mediaPaths(value: unknown): unknown[] {\n const values = Array.isArray(value) ? value : [value];\n return values.map((item) => (item !== null && typeof item === \"object\" ? (item as { path?: unknown }).path : item));\n}\n\nfunction list(value: unknown): string[] {\n const values = Array.isArray(value) ? value : [value];\n return values.filter((item): item is string => typeof item === \"string\" && item !== \"\");\n}\n\n/** Calls `onField` for every field with a value, nested fields included. */\nfunction visit(\n fields: FieldMap,\n data: Record<string, unknown>,\n path: PathSegment[],\n onField: (field: AnyField, value: unknown, path: PathSegment[]) => void,\n): void {\n for (const [key, field] of Object.entries(fields)) {\n const value = data[key];\n if (value === undefined || value === null) continue;\n onField(field, value, [...path, key]);\n if (field.kind === \"object\" && isRecord(value)) visit(field.fields, value, [...path, key], onField);\n if (field.kind === \"repeater\" && Array.isArray(value)) {\n value.forEach((row, index) => {\n if (isRecord(row)) visit(field.fields, row, [...path, key, index], onField);\n });\n }\n }\n}\n\n/** Keys in the stored JSON that the model does not have; Mica drops them on the next save. */\nfunction unknownKeys(fields: FieldMap, raw: Record<string, unknown>, path: PathSegment[]): string[] {\n const messages: string[] = [];\n for (const [key, value] of Object.entries(raw)) {\n const at = [...path, key].join(\".\");\n const field = fields[key];\n if (!field) {\n messages.push(`${at}: the model has no such field. Mica drops it on the next save.`);\n } else if (field.kind === \"richText\") {\n messages.push(`${at}: rich text belongs in ${[...path, key].join(\".\")}${DOCUMENT_EXTENSION}, not in the JSON.`);\n } else if (field.kind === \"object\" && isRecord(value)) {\n messages.push(...unknownKeys(field.fields, value, [...path, key]));\n } else if (field.kind === \"repeater\" && Array.isArray(value)) {\n value.forEach((row, index) => {\n if (isRecord(row)) messages.push(...unknownKeys(field.fields, row, [...path, key, index]));\n });\n }\n }\n return messages;\n}\n\n/** Whether a sidecar's dotted path leads to a rich-text field. */\nfunction namesRichText(fields: FieldMap, path: PathSegment[]): boolean {\n let map: FieldMap | undefined = fields;\n for (const [index, segment] of path.entries()) {\n if (typeof segment === \"number\") continue;\n const field: AnyField | undefined = map?.[segment];\n if (!field) return false;\n if (index === path.length - 1) return field.kind === \"richText\";\n map = field.kind === \"object\" || field.kind === \"repeater\" ? field.fields : undefined;\n }\n return false;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** The report as a person or an agent reads it. */\nexport function formatReport(report: CheckReport, root: string): string {\n const errors = report.problems.filter((problem) => problem.level === \"error\").length;\n const warnings = report.problems.length - errors;\n const lines = [\n report.model\n ? `Checked ${report.entries} ${report.entries === 1 ? \"entry\" : \"entries\"} in ${report.collections} ` +\n `${report.collections === 1 ? \"collection\" : \"collections\"} and ${report.singletons} ` +\n `${report.singletons === 1 ? \"singleton\" : \"singletons\"} in ${relative(process.cwd(), root) || \".\"}.`\n : `Could not read the content model in ${relative(process.cwd(), root) || \".\"}, so no entry was checked.`,\n \"\",\n ];\n for (const problem of report.problems) {\n lines.push(` ${problem.level === \"error\" ? \"error \" : \"warning\"} ${problem.path}`);\n for (const line of problem.message.split(\"\\n\")) lines.push(` ${line.trim()}`);\n }\n if (report.problems.length > 0) lines.push(\"\");\n lines.push(`${errors} ${errors === 1 ? \"error\" : \"errors\"}, ${warnings} ${warnings === 1 ? \"warning\" : \"warnings\"}.`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n"]}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `npx @micacms/editor` — Mica against a project on this machine.
|
|
3
|
+
* `npx @micacms/editor start` — Mica against a project on this machine.
|
|
4
4
|
*
|
|
5
5
|
* The point of this file is that adopting Mica should not mean adopting this
|
|
6
6
|
* repository. Everything it does is set environment variables and hand over to
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - it binds to loopback. A container binds 0.0.0.0 because it has to; a
|
|
13
13
|
* laptop does not, and this editor has no authentication. Anything
|
|
14
14
|
* reachable by anyone else runs @micacms/self-hosted with an identity
|
|
15
|
-
* provider — see docs/self-hosting
|
|
15
|
+
* provider — see https://micacms.com/docs/self-hosting/.
|
|
16
16
|
* - it talks to a person instead of to a log collector: one short banner
|
|
17
17
|
* saying where the editor is and what happens when you save, and the
|
|
18
18
|
* browser opened on it. `MICA_CLI` is how the server is told which of the
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
* account and no session, so nothing is written outside the content
|
|
23
23
|
* directory.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* A flag wins over the environment, and the environment over a default. No
|
|
26
|
+
* `.env` file is read: the project's `.env` belongs to the site.
|
|
27
27
|
*/
|
|
28
28
|
export interface Options {
|
|
29
|
-
|
|
29
|
+
/** Undefined means "not given": `MICA_CONTENT_ROOT`, else `./content`. */
|
|
30
|
+
content?: string;
|
|
30
31
|
port?: string;
|
|
31
32
|
host?: string;
|
|
32
33
|
/** Undefined means "decide from the terminal" — see `shouldOpenBrowser`. */
|
|
@@ -39,5 +40,9 @@ export declare function parse(argv: string[]): Options;
|
|
|
39
40
|
* container and a CI job all get a server and no surprises.
|
|
40
41
|
*/
|
|
41
42
|
export declare function shouldOpenBrowser(options: Options, env: NodeJS.ProcessEnv): boolean;
|
|
43
|
+
/** The content root: the flag or path, else `MICA_CONTENT_ROOT`, else `./content`. */
|
|
44
|
+
export declare function contentRoot(options: Options, env: NodeJS.ProcessEnv): string;
|
|
45
|
+
/** What the server reads: a flag wins over the environment, and the environment over a default. */
|
|
46
|
+
export declare function serverEnvironment(options: Options, env: NodeJS.ProcessEnv, content: string): Record<string, string>;
|
|
42
47
|
export declare function main(): Promise<void>;
|
|
43
48
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAcH,MAAM,WAAW,OAAO;IACtB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA+BD,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAgD7C;AAkBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAGnF;AAKD,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAE5E;AAED,mGAAmG;AACnG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAanH;AAED,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA+C1C"}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `npx @micacms/editor` — Mica against a project on this machine.
|
|
3
|
+
* `npx @micacms/editor start` — Mica against a project on this machine.
|
|
4
4
|
*
|
|
5
5
|
* The point of this file is that adopting Mica should not mean adopting this
|
|
6
6
|
* repository. Everything it does is set environment variables and hand over to
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - it binds to loopback. A container binds 0.0.0.0 because it has to; a
|
|
13
13
|
* laptop does not, and this editor has no authentication. Anything
|
|
14
14
|
* reachable by anyone else runs @micacms/self-hosted with an identity
|
|
15
|
-
* provider — see docs/self-hosting
|
|
15
|
+
* provider — see https://micacms.com/docs/self-hosting/.
|
|
16
16
|
* - it talks to a person instead of to a log collector: one short banner
|
|
17
17
|
* saying where the editor is and what happens when you save, and the
|
|
18
18
|
* browser opened on it. `MICA_CLI` is how the server is told which of the
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* account and no session, so nothing is written outside the content
|
|
23
23
|
* directory.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* A flag wins over the environment, and the environment over a default. No
|
|
26
|
+
* `.env` file is read: the project's `.env` belongs to the site.
|
|
27
27
|
*/
|
|
28
28
|
import { existsSync, realpathSync } from "node:fs";
|
|
29
29
|
import path from "node:path";
|
|
@@ -32,17 +32,24 @@ import { MANIFEST_PATH } from "@micacms/core";
|
|
|
32
32
|
import { DEFAULT_PORT, VERSION } from "@micacms/runtime";
|
|
33
33
|
import { checkContent, formatReport } from "./check.js";
|
|
34
34
|
import { formatInit, initProject, InitError } from "./init.js";
|
|
35
|
+
import { formatImport, importCollections, ImportError } from "./import.js";
|
|
36
|
+
import { formatMigration, migrateLayout } from "./migrate.js";
|
|
35
37
|
const USAGE = `
|
|
36
38
|
mica — edit your Astro project's content, on this machine
|
|
37
39
|
|
|
38
40
|
Usage
|
|
39
|
-
$ npx @micacms/editor [content
|
|
41
|
+
$ npx @micacms/editor start [content dir] open the editor
|
|
40
42
|
$ npx @micacms/editor init add Mica to this Astro project
|
|
41
43
|
$ npx @micacms/editor check [content dir] check every entry against the model
|
|
44
|
+
$ npx @micacms/editor migrate-layout [dir] move entries into the layout of the model
|
|
45
|
+
$ npx @micacms/editor import [names] move Astro collections into Mica
|
|
46
|
+
--from <file> Astro's data store (default: node_modules/.astro/data-store.json)
|
|
47
|
+
--map <c.field=key> take a field from another key; @body is the Markdown body
|
|
48
|
+
--order <c=field> the initial order of a sortable collection
|
|
42
49
|
|
|
43
50
|
With @micacms/editor installed in the project, \`npx mica\` is the same command.
|
|
44
51
|
|
|
45
|
-
Options
|
|
52
|
+
Options of start
|
|
46
53
|
-c, --content <dir> The content root (default: ./content)
|
|
47
54
|
-p, --port <number> Port to listen on (default: ${DEFAULT_PORT})
|
|
48
55
|
--host <address> Address to bind (default: 127.0.0.1)
|
|
@@ -56,7 +63,7 @@ const USAGE = `
|
|
|
56
63
|
container with an identity provider. See https://micacms.com/docs/self-hosting/.
|
|
57
64
|
`;
|
|
58
65
|
export function parse(argv) {
|
|
59
|
-
const options = {
|
|
66
|
+
const options = {};
|
|
60
67
|
for (let index = 0; index < argv.length; index++) {
|
|
61
68
|
const flag = argv[index];
|
|
62
69
|
const value = () => {
|
|
@@ -92,7 +99,7 @@ export function parse(argv) {
|
|
|
92
99
|
process.stdout.write(`${VERSION}\n`);
|
|
93
100
|
process.exit(0);
|
|
94
101
|
default:
|
|
95
|
-
// A bare path is the content root, so `npx @micacms/editor ./site/content`
|
|
102
|
+
// A bare path is the content root, so `npx @micacms/editor start ./site/content`
|
|
96
103
|
// does what it looks like it does.
|
|
97
104
|
if (!flag.startsWith("-"))
|
|
98
105
|
options.content = flag;
|
|
@@ -113,7 +120,7 @@ function report(title, lines) {
|
|
|
113
120
|
process.stderr.write(`\n ${title}\n\n${body}\n\n`);
|
|
114
121
|
}
|
|
115
122
|
function fail(message) {
|
|
116
|
-
report(message, ["Run `npx @micacms/editor --help` to see every option."]);
|
|
123
|
+
report(message, ["Run `npx @micacms/editor --help` to see every command and option."]);
|
|
117
124
|
process.exit(64); // EX_USAGE
|
|
118
125
|
}
|
|
119
126
|
/**
|
|
@@ -126,20 +133,52 @@ export function shouldOpenBrowser(options, env) {
|
|
|
126
133
|
return options.open;
|
|
127
134
|
return Boolean(process.stdout.isTTY) && !env["CI"];
|
|
128
135
|
}
|
|
136
|
+
/** A variable's value, with a blank one counted as not set. */
|
|
137
|
+
const set = (value) => (value?.trim() ? value : undefined);
|
|
138
|
+
/** The content root: the flag or path, else `MICA_CONTENT_ROOT`, else `./content`. */
|
|
139
|
+
export function contentRoot(options, env) {
|
|
140
|
+
return path.resolve(options.content ?? set(env["MICA_CONTENT_ROOT"]) ?? "./content");
|
|
141
|
+
}
|
|
142
|
+
/** What the server reads: a flag wins over the environment, and the environment over a default. */
|
|
143
|
+
export function serverEnvironment(options, env, content) {
|
|
144
|
+
const port = options.port ?? set(env["PORT"]) ?? String(DEFAULT_PORT);
|
|
145
|
+
return {
|
|
146
|
+
// The checks ran on this path, so it is the one the server gets.
|
|
147
|
+
MICA_CONTENT_ROOT: content,
|
|
148
|
+
HOST: options.host ?? set(env["HOST"]) ?? "127.0.0.1",
|
|
149
|
+
PORT: port,
|
|
150
|
+
// It must agree with the port: it scopes the cookie and the Origin check.
|
|
151
|
+
MICA_PUBLIC_URL: (!options.port && set(env["MICA_PUBLIC_URL"])) || `http://localhost:${port}`,
|
|
152
|
+
// A person, not a log collector: one banner instead of startup log lines.
|
|
153
|
+
MICA_CLI: "true",
|
|
154
|
+
MICA_OPEN: String(shouldOpenBrowser(options, env)),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
129
157
|
export async function main() {
|
|
130
158
|
const [command, ...rest] = process.argv.slice(2);
|
|
131
159
|
if (command === "init")
|
|
132
160
|
return init(rest);
|
|
133
161
|
if (command === "check")
|
|
134
162
|
return check(rest);
|
|
135
|
-
|
|
136
|
-
|
|
163
|
+
if (command === "migrate-layout")
|
|
164
|
+
return migrate(rest);
|
|
165
|
+
if (command === "import")
|
|
166
|
+
return importCommand(rest);
|
|
167
|
+
// The first word is always a command, so that a directory named `init` is not one.
|
|
168
|
+
if (command === undefined || command === "-h" || command === "--help")
|
|
169
|
+
return void process.stdout.write(`${USAGE}\n`);
|
|
170
|
+
if (command === "-v" || command === "--version")
|
|
171
|
+
return void process.stdout.write(`${VERSION}\n`);
|
|
172
|
+
if (command !== "start")
|
|
173
|
+
fail(`Unknown command "${command}". To open the editor: npx @micacms/editor start`);
|
|
174
|
+
const options = parse(rest);
|
|
175
|
+
const content = contentRoot(options, process.env);
|
|
137
176
|
if (!existsSync(content)) {
|
|
138
177
|
report(`There is no content directory at ${content}.`, [
|
|
139
178
|
"Mica edits the directory your Astro project publishes content into —",
|
|
140
179
|
"the `root` in your `defineSite({ root: \"./content\" })`.",
|
|
141
180
|
"",
|
|
142
|
-
" npx @micacms/editor ./path/to/content",
|
|
181
|
+
" npx @micacms/editor start ./path/to/content",
|
|
143
182
|
"",
|
|
144
183
|
"If this is a new project, create the directory and Mica writes the first",
|
|
145
184
|
"entry into it.",
|
|
@@ -160,22 +199,7 @@ export async function main() {
|
|
|
160
199
|
]);
|
|
161
200
|
process.exit(78); // EX_CONFIG
|
|
162
201
|
}
|
|
163
|
-
|
|
164
|
-
CONTENT_ROOT: content,
|
|
165
|
-
HOST: options.host ?? "127.0.0.1",
|
|
166
|
-
PORT: options.port ?? String(DEFAULT_PORT),
|
|
167
|
-
// Tells the server it is talking to a person: one banner instead of the
|
|
168
|
-
// startup log lines a collector wants.
|
|
169
|
-
MICA_CLI: "true",
|
|
170
|
-
MICA_OPEN: String(shouldOpenBrowser(options, process.env)),
|
|
171
|
-
};
|
|
172
|
-
for (const [key, value] of Object.entries(defaults)) {
|
|
173
|
-
process.env[key] ??= value;
|
|
174
|
-
}
|
|
175
|
-
// MICA_PUBLIC_URL has to agree with the port, and only matters here for
|
|
176
|
-
// cookie scope and the Origin check — both of which are this process's own
|
|
177
|
-
// address.
|
|
178
|
-
process.env["MICA_PUBLIC_URL"] ??= `http://localhost:${process.env["PORT"]}`;
|
|
202
|
+
Object.assign(process.env, serverEnvironment(options, process.env, content));
|
|
179
203
|
// The server is imported rather than spawned: one process, one signal
|
|
180
204
|
// handler, and a stack trace that points at the real thing.
|
|
181
205
|
await import("./index.js");
|
|
@@ -194,13 +218,71 @@ function init(argv) {
|
|
|
194
218
|
}
|
|
195
219
|
}
|
|
196
220
|
async function check(argv) {
|
|
197
|
-
const
|
|
198
|
-
const root = path.resolve(content);
|
|
221
|
+
const root = contentRoot(parse(argv), process.env);
|
|
199
222
|
const result = await checkContent(root);
|
|
200
223
|
process.stdout.write(formatReport(result, root));
|
|
201
224
|
if (result.problems.some((problem) => problem.level === "error"))
|
|
202
225
|
process.exit(1);
|
|
203
226
|
}
|
|
227
|
+
async function migrate(argv) {
|
|
228
|
+
const result = await migrateLayout(contentRoot(parse(argv), process.env));
|
|
229
|
+
process.stdout.write(formatMigration(result));
|
|
230
|
+
if (result.conflicts.length > 0)
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
async function importCommand(argv) {
|
|
234
|
+
// `astro sync` and `astro build` write the store into node_modules; `astro dev` into .astro.
|
|
235
|
+
const found = ["node_modules/.astro/data-store.json", ".astro/data-store.json"].find((file) => existsSync(file));
|
|
236
|
+
const options = { store: found ?? "node_modules/.astro/data-store.json", map: {}, order: {}, only: [], content: undefined };
|
|
237
|
+
for (let index = 0; index < argv.length; index++) {
|
|
238
|
+
const flag = argv[index];
|
|
239
|
+
const value = () => {
|
|
240
|
+
const next = argv[++index];
|
|
241
|
+
if (next === undefined || next.startsWith("-"))
|
|
242
|
+
fail(`${flag} needs a value.`);
|
|
243
|
+
return next;
|
|
244
|
+
};
|
|
245
|
+
const pair = (text) => {
|
|
246
|
+
const at = text.indexOf("=");
|
|
247
|
+
if (at < 1 || at === text.length - 1)
|
|
248
|
+
fail(`${flag} takes name=value, for example team.image.alt=alt.`);
|
|
249
|
+
return [text.slice(0, at), text.slice(at + 1)];
|
|
250
|
+
};
|
|
251
|
+
if (flag === "--from")
|
|
252
|
+
options.store = value();
|
|
253
|
+
else if (flag === "--content" || flag === "-c")
|
|
254
|
+
options.content = value();
|
|
255
|
+
else if (flag === "--map") {
|
|
256
|
+
const [to, from] = pair(value());
|
|
257
|
+
options.map[to] = from;
|
|
258
|
+
}
|
|
259
|
+
else if (flag === "--order") {
|
|
260
|
+
const [name, field] = pair(value());
|
|
261
|
+
options.order[name] = field;
|
|
262
|
+
}
|
|
263
|
+
else if (flag.startsWith("-"))
|
|
264
|
+
fail(`Unknown option "${flag}".`);
|
|
265
|
+
else
|
|
266
|
+
options.only.push(flag);
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const result = await importCollections({
|
|
270
|
+
root: contentRoot({ content: options.content }, process.env),
|
|
271
|
+
project: process.cwd(),
|
|
272
|
+
store: path.resolve(options.store),
|
|
273
|
+
map: options.map,
|
|
274
|
+
order: options.order,
|
|
275
|
+
only: options.only,
|
|
276
|
+
});
|
|
277
|
+
process.stdout.write(formatImport(result));
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (!(error instanceof ImportError))
|
|
281
|
+
throw error;
|
|
282
|
+
report(error.message, []);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
204
286
|
// Only when this file *is* the command. Importing it, as the tests do, must
|
|
205
287
|
// not start a server.
|
|
206
288
|
//
|