@micacms/editor 0.6.0-rc.1 → 0.6.0
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 +2 -1
- package/dist/banner.js +1 -1
- package/dist/banner.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +74 -7
- package/dist/cli.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/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/init.js +3 -3
- package/dist/init.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -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
|
|
@@ -45,6 +45,7 @@ keeps Publish out of the editor entirely).
|
|
|
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
47
|
npx @micacms/editor migrate-layout # move entries after a layout change
|
|
48
|
+
npx @micacms/editor import # move Astro collections into Mica
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
`init` writes a starter `src/mica.ts` and `src/content.config.ts`, and never
|
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/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
|
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
|
|
@@ -32,19 +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";
|
|
35
36
|
import { formatMigration, migrateLayout } from "./migrate.js";
|
|
36
37
|
const USAGE = `
|
|
37
38
|
mica — edit your Astro project's content, on this machine
|
|
38
39
|
|
|
39
40
|
Usage
|
|
40
|
-
$ npx @micacms/editor [content
|
|
41
|
+
$ npx @micacms/editor start [content dir] open the editor
|
|
41
42
|
$ npx @micacms/editor init add Mica to this Astro project
|
|
42
43
|
$ npx @micacms/editor check [content dir] check every entry against the model
|
|
43
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
|
|
44
49
|
|
|
45
50
|
With @micacms/editor installed in the project, \`npx mica\` is the same command.
|
|
46
51
|
|
|
47
|
-
Options
|
|
52
|
+
Options of start
|
|
48
53
|
-c, --content <dir> The content root (default: ./content)
|
|
49
54
|
-p, --port <number> Port to listen on (default: ${DEFAULT_PORT})
|
|
50
55
|
--host <address> Address to bind (default: 127.0.0.1)
|
|
@@ -94,7 +99,7 @@ export function parse(argv) {
|
|
|
94
99
|
process.stdout.write(`${VERSION}\n`);
|
|
95
100
|
process.exit(0);
|
|
96
101
|
default:
|
|
97
|
-
// 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`
|
|
98
103
|
// does what it looks like it does.
|
|
99
104
|
if (!flag.startsWith("-"))
|
|
100
105
|
options.content = flag;
|
|
@@ -115,7 +120,7 @@ function report(title, lines) {
|
|
|
115
120
|
process.stderr.write(`\n ${title}\n\n${body}\n\n`);
|
|
116
121
|
}
|
|
117
122
|
function fail(message) {
|
|
118
|
-
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."]);
|
|
119
124
|
process.exit(64); // EX_USAGE
|
|
120
125
|
}
|
|
121
126
|
/**
|
|
@@ -157,14 +162,23 @@ export async function main() {
|
|
|
157
162
|
return check(rest);
|
|
158
163
|
if (command === "migrate-layout")
|
|
159
164
|
return migrate(rest);
|
|
160
|
-
|
|
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);
|
|
161
175
|
const content = contentRoot(options, process.env);
|
|
162
176
|
if (!existsSync(content)) {
|
|
163
177
|
report(`There is no content directory at ${content}.`, [
|
|
164
178
|
"Mica edits the directory your Astro project publishes content into —",
|
|
165
179
|
"the `root` in your `defineSite({ root: \"./content\" })`.",
|
|
166
180
|
"",
|
|
167
|
-
" npx @micacms/editor ./path/to/content",
|
|
181
|
+
" npx @micacms/editor start ./path/to/content",
|
|
168
182
|
"",
|
|
169
183
|
"If this is a new project, create the directory and Mica writes the first",
|
|
170
184
|
"entry into it.",
|
|
@@ -216,6 +230,59 @@ async function migrate(argv) {
|
|
|
216
230
|
if (result.conflicts.length > 0)
|
|
217
231
|
process.exit(1);
|
|
218
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
|
+
}
|
|
219
286
|
// Only when this file *is* the command. Importing it, as the tests do, must
|
|
220
287
|
// not start a server.
|
|
221
288
|
//
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * `npx @micacms/editor` — Mica against a project on this machine.\n *\n * The point of this file is that adopting Mica should not mean adopting this\n * repository. Everything it does is set environment variables and hand over to\n * the same entry point a container runs, so there is one server with one\n * configuration path and this is a front door rather than a second one.\n *\n * Two defaults differ from a container's, and both are deliberate:\n *\n * - it binds to loopback. A container binds 0.0.0.0 because it has to; a\n * laptop does not, and this editor has no authentication. Anything\n * reachable by anyone else runs @micacms/self-hosted with an identity\n * provider — see https://micacms.com/docs/self-hosting/.\n * - it talks to a person instead of to a log collector: one short banner\n * saying where the editor is and what happens when you save, and the\n * browser opened on it. `MICA_CLI` is how the server is told which of the\n * two audiences it has.\n *\n * There is no state to keep anywhere: the local editor has no database, no\n * account and no session, so nothing is written outside the content\n * directory.\n *\n * A flag wins over the environment, and the environment over a default. No\n * `.env` file is read: the project's `.env` belongs to the site.\n */\n\nimport { existsSync, realpathSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { MANIFEST_PATH } from \"@micacms/core\";\nimport { DEFAULT_PORT, VERSION } from \"@micacms/runtime\";\n\nimport { checkContent, formatReport } from \"./check.js\";\nimport { formatInit, initProject, InitError } from \"./init.js\";\nimport { formatMigration, migrateLayout } from \"./migrate.js\";\n\nexport interface Options {\n /** Undefined means \"not given\": `MICA_CONTENT_ROOT`, else `./content`. */\n content?: string;\n port?: string;\n host?: string;\n /** Undefined means \"decide from the terminal\" — see `shouldOpenBrowser`. */\n open?: boolean;\n}\n\nconst USAGE = `\n mica — edit your Astro project's content, on this machine\n\n Usage\n $ npx @micacms/editor [content directory] open the editor\n $ npx @micacms/editor init add Mica to this Astro project\n $ npx @micacms/editor check [content dir] check every entry against the model\n $ npx @micacms/editor migrate-layout [dir] move entries into the layout of the model\n\n With @micacms/editor installed in the project, \\`npx mica\\` is the same command.\n\n Options\n -c, --content <dir> The content root (default: ./content)\n -p, --port <number> Port to listen on (default: ${DEFAULT_PORT})\n --host <address> Address to bind (default: 127.0.0.1)\n --no-open Do not open the browser\n -h, --help Show this\n -v, --version Print the version\n\n There is no sign-in here, because there is nothing to sign in to: this mode\n has no identity provider, no account and no user. That is right for your own\n machine and wrong everywhere else — a Mica other people reach runs as a\n container with an identity provider. See https://micacms.com/docs/self-hosting/.\n`;\n\nexport function parse(argv: string[]): Options {\n const options: Options = {};\n\n for (let index = 0; index < argv.length; index++) {\n const flag = argv[index]!;\n const value = (): string => {\n const next = argv[++index];\n if (next === undefined || next.startsWith(\"-\")) fail(`${flag} needs a value.`);\n return next!;\n };\n\n switch (flag) {\n case \"-c\":\n case \"--content\":\n options.content = value();\n break;\n case \"-p\":\n case \"--port\":\n options.port = value();\n break;\n case \"--host\":\n options.host = value();\n break;\n case \"--open\":\n options.open = true;\n break;\n case \"--no-open\":\n options.open = false;\n break;\n case \"-h\":\n case \"--help\":\n process.stdout.write(`${USAGE}\\n`);\n process.exit(0);\n\n case \"-v\":\n case \"--version\":\n process.stdout.write(`${VERSION}\\n`);\n process.exit(0);\n\n default:\n // A bare path is the content root, so `npx @micacms/editor ./site/content`\n // does what it looks like it does.\n if (!flag.startsWith(\"-\")) options.content = flag;\n else fail(`Unknown option \"${flag}\".`);\n }\n }\n\n return options;\n}\n\n/**\n * A problem the person can fix, said the way a person reads.\n *\n * The title is the fact, the lines under it are what to do about it. Anything\n * printed here has already decided the command is over: the caller exits.\n */\nfunction report(title: string, lines: string[]): void {\n const body = lines.map((line) => (line ? ` ${line}` : \"\")).join(\"\\n\");\n process.stderr.write(`\\n ${title}\\n\\n${body}\\n\\n`);\n}\n\nfunction fail(message: string): never {\n report(message, [\"Run `npx @micacms/editor --help` to see every option.\"]);\n process.exit(64); // EX_USAGE\n}\n\n/**\n * Opening a browser is a courtesy, so it is offered only where it can be\n * welcome: an interactive terminal that somebody is sitting at. A script, a\n * container and a CI job all get a server and no surprises.\n */\nexport function shouldOpenBrowser(options: Options, env: NodeJS.ProcessEnv): boolean {\n if (options.open !== undefined) return options.open;\n return Boolean(process.stdout.isTTY) && !env[\"CI\"];\n}\n\n/** A variable's value, with a blank one counted as not set. */\nconst set = (value: string | undefined): string | undefined => (value?.trim() ? value : undefined);\n\n/** The content root: the flag or path, else `MICA_CONTENT_ROOT`, else `./content`. */\nexport function contentRoot(options: Options, env: NodeJS.ProcessEnv): string {\n return path.resolve(options.content ?? set(env[\"MICA_CONTENT_ROOT\"]) ?? \"./content\");\n}\n\n/** What the server reads: a flag wins over the environment, and the environment over a default. */\nexport function serverEnvironment(options: Options, env: NodeJS.ProcessEnv, content: string): Record<string, string> {\n const port = options.port ?? set(env[\"PORT\"]) ?? String(DEFAULT_PORT);\n return {\n // The checks ran on this path, so it is the one the server gets.\n MICA_CONTENT_ROOT: content,\n HOST: options.host ?? set(env[\"HOST\"]) ?? \"127.0.0.1\",\n PORT: port,\n // It must agree with the port: it scopes the cookie and the Origin check.\n MICA_PUBLIC_URL: (!options.port && set(env[\"MICA_PUBLIC_URL\"])) || `http://localhost:${port}`,\n // A person, not a log collector: one banner instead of startup log lines.\n MICA_CLI: \"true\",\n MICA_OPEN: String(shouldOpenBrowser(options, env)),\n };\n}\n\nexport async function main(): Promise<void> {\n const [command, ...rest] = process.argv.slice(2);\n if (command === \"init\") return init(rest);\n if (command === \"check\") return check(rest);\n if (command === \"migrate-layout\") return migrate(rest);\n\n const options = parse(process.argv.slice(2));\n const content = contentRoot(options, process.env);\n\n if (!existsSync(content)) {\n report(`There is no content directory at ${content}.`, [\n \"Mica edits the directory your Astro project publishes content into —\",\n \"the `root` in your `defineSite({ root: \\\"./content\\\" })`.\",\n \"\",\n \" npx @micacms/editor ./path/to/content\",\n \"\",\n \"If this is a new project, create the directory and Mica writes the first\",\n \"entry into it.\",\n ]);\n process.exit(64); // EX_USAGE\n }\n\n // A far more useful message than the server's, because here we know the\n // project is on this machine and can name the command that fixes it.\n if (!existsSync(path.join(content, MANIFEST_PATH))) {\n report(`There is no content model at ${path.join(content, MANIFEST_PATH)}.`, [\n \"Mica ships without one: your Astro project defines the model, and building\",\n \"the project writes it there. Run this in the project, then start Mica again:\",\n \"\",\n \" npx astro sync # or build, or start the dev server once\",\n \"\",\n \"If you have not defined a model yet, run `npx @micacms/editor init`, or see\",\n \"https://micacms.com/docs/getting-started/.\",\n ]);\n process.exit(78); // EX_CONFIG\n }\n\n Object.assign(process.env, serverEnvironment(options, process.env, content));\n\n // The server is imported rather than spawned: one process, one signal\n // handler, and a stack trace that points at the real thing.\n await import(\"./index.js\");\n}\n\nfunction init(argv: string[]): void {\n if (argv.length > 0) fail(`init takes no options; it works in the current directory.`);\n try {\n process.stdout.write(formatInit(initProject(process.cwd())));\n } catch (error) {\n if (!(error instanceof InitError)) throw error;\n report(error.message, []);\n process.exit(64); // EX_USAGE\n }\n}\n\nasync function check(argv: string[]): Promise<void> {\n const root = contentRoot(parse(argv), process.env);\n const result = await checkContent(root);\n process.stdout.write(formatReport(result, root));\n if (result.problems.some((problem) => problem.level === \"error\")) process.exit(1);\n}\n\nasync function migrate(argv: string[]): Promise<void> {\n const result = await migrateLayout(contentRoot(parse(argv), process.env));\n process.stdout.write(formatMigration(result));\n if (result.conflicts.length > 0) process.exit(1);\n}\n\n// Only when this file *is* the command. Importing it, as the tests do, must\n// not start a server.\n//\n// `realpathSync` matters: npm and npx run this file through a symlink in\n// `node_modules/.bin`, and Node resolves `import.meta.url` to the symlink's\n// real target. Comparing against the raw `argv[1]` never matched there, so\n// `npx @micacms/editor` silently did nothing.\nif (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {\n main().catch((error: unknown) => {\n process.stderr.write(`\\n${error instanceof Error ? error.stack : String(error)}\\n`);\n process.exit(1);\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * `npx @micacms/editor start` — Mica against a project on this machine.\n *\n * The point of this file is that adopting Mica should not mean adopting this\n * repository. Everything it does is set environment variables and hand over to\n * the same entry point a container runs, so there is one server with one\n * configuration path and this is a front door rather than a second one.\n *\n * Two defaults differ from a container's, and both are deliberate:\n *\n * - it binds to loopback. A container binds 0.0.0.0 because it has to; a\n * laptop does not, and this editor has no authentication. Anything\n * reachable by anyone else runs @micacms/self-hosted with an identity\n * provider — see https://micacms.com/docs/self-hosting/.\n * - it talks to a person instead of to a log collector: one short banner\n * saying where the editor is and what happens when you save, and the\n * browser opened on it. `MICA_CLI` is how the server is told which of the\n * two audiences it has.\n *\n * There is no state to keep anywhere: the local editor has no database, no\n * account and no session, so nothing is written outside the content\n * directory.\n *\n * A flag wins over the environment, and the environment over a default. No\n * `.env` file is read: the project's `.env` belongs to the site.\n */\n\nimport { existsSync, realpathSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { MANIFEST_PATH } from \"@micacms/core\";\nimport { DEFAULT_PORT, VERSION } from \"@micacms/runtime\";\n\nimport { checkContent, formatReport } from \"./check.js\";\nimport { formatInit, initProject, InitError } from \"./init.js\";\nimport { formatImport, importCollections, ImportError } from \"./import.js\";\nimport { formatMigration, migrateLayout } from \"./migrate.js\";\n\nexport interface Options {\n /** Undefined means \"not given\": `MICA_CONTENT_ROOT`, else `./content`. */\n content?: string;\n port?: string;\n host?: string;\n /** Undefined means \"decide from the terminal\" — see `shouldOpenBrowser`. */\n open?: boolean;\n}\n\nconst USAGE = `\n mica — edit your Astro project's content, on this machine\n\n Usage\n $ npx @micacms/editor start [content dir] open the editor\n $ npx @micacms/editor init add Mica to this Astro project\n $ npx @micacms/editor check [content dir] check every entry against the model\n $ npx @micacms/editor migrate-layout [dir] move entries into the layout of the model\n $ npx @micacms/editor import [names] move Astro collections into Mica\n --from <file> Astro's data store (default: node_modules/.astro/data-store.json)\n --map <c.field=key> take a field from another key; @body is the Markdown body\n --order <c=field> the initial order of a sortable collection\n\n With @micacms/editor installed in the project, \\`npx mica\\` is the same command.\n\n Options of start\n -c, --content <dir> The content root (default: ./content)\n -p, --port <number> Port to listen on (default: ${DEFAULT_PORT})\n --host <address> Address to bind (default: 127.0.0.1)\n --no-open Do not open the browser\n -h, --help Show this\n -v, --version Print the version\n\n There is no sign-in here, because there is nothing to sign in to: this mode\n has no identity provider, no account and no user. That is right for your own\n machine and wrong everywhere else — a Mica other people reach runs as a\n container with an identity provider. See https://micacms.com/docs/self-hosting/.\n`;\n\nexport function parse(argv: string[]): Options {\n const options: Options = {};\n\n for (let index = 0; index < argv.length; index++) {\n const flag = argv[index]!;\n const value = (): string => {\n const next = argv[++index];\n if (next === undefined || next.startsWith(\"-\")) fail(`${flag} needs a value.`);\n return next!;\n };\n\n switch (flag) {\n case \"-c\":\n case \"--content\":\n options.content = value();\n break;\n case \"-p\":\n case \"--port\":\n options.port = value();\n break;\n case \"--host\":\n options.host = value();\n break;\n case \"--open\":\n options.open = true;\n break;\n case \"--no-open\":\n options.open = false;\n break;\n case \"-h\":\n case \"--help\":\n process.stdout.write(`${USAGE}\\n`);\n process.exit(0);\n\n case \"-v\":\n case \"--version\":\n process.stdout.write(`${VERSION}\\n`);\n process.exit(0);\n\n default:\n // A bare path is the content root, so `npx @micacms/editor start ./site/content`\n // does what it looks like it does.\n if (!flag.startsWith(\"-\")) options.content = flag;\n else fail(`Unknown option \"${flag}\".`);\n }\n }\n\n return options;\n}\n\n/**\n * A problem the person can fix, said the way a person reads.\n *\n * The title is the fact, the lines under it are what to do about it. Anything\n * printed here has already decided the command is over: the caller exits.\n */\nfunction report(title: string, lines: string[]): void {\n const body = lines.map((line) => (line ? ` ${line}` : \"\")).join(\"\\n\");\n process.stderr.write(`\\n ${title}\\n\\n${body}\\n\\n`);\n}\n\nfunction fail(message: string): never {\n report(message, [\"Run `npx @micacms/editor --help` to see every command and option.\"]);\n process.exit(64); // EX_USAGE\n}\n\n/**\n * Opening a browser is a courtesy, so it is offered only where it can be\n * welcome: an interactive terminal that somebody is sitting at. A script, a\n * container and a CI job all get a server and no surprises.\n */\nexport function shouldOpenBrowser(options: Options, env: NodeJS.ProcessEnv): boolean {\n if (options.open !== undefined) return options.open;\n return Boolean(process.stdout.isTTY) && !env[\"CI\"];\n}\n\n/** A variable's value, with a blank one counted as not set. */\nconst set = (value: string | undefined): string | undefined => (value?.trim() ? value : undefined);\n\n/** The content root: the flag or path, else `MICA_CONTENT_ROOT`, else `./content`. */\nexport function contentRoot(options: Options, env: NodeJS.ProcessEnv): string {\n return path.resolve(options.content ?? set(env[\"MICA_CONTENT_ROOT\"]) ?? \"./content\");\n}\n\n/** What the server reads: a flag wins over the environment, and the environment over a default. */\nexport function serverEnvironment(options: Options, env: NodeJS.ProcessEnv, content: string): Record<string, string> {\n const port = options.port ?? set(env[\"PORT\"]) ?? String(DEFAULT_PORT);\n return {\n // The checks ran on this path, so it is the one the server gets.\n MICA_CONTENT_ROOT: content,\n HOST: options.host ?? set(env[\"HOST\"]) ?? \"127.0.0.1\",\n PORT: port,\n // It must agree with the port: it scopes the cookie and the Origin check.\n MICA_PUBLIC_URL: (!options.port && set(env[\"MICA_PUBLIC_URL\"])) || `http://localhost:${port}`,\n // A person, not a log collector: one banner instead of startup log lines.\n MICA_CLI: \"true\",\n MICA_OPEN: String(shouldOpenBrowser(options, env)),\n };\n}\n\nexport async function main(): Promise<void> {\n const [command, ...rest] = process.argv.slice(2);\n if (command === \"init\") return init(rest);\n if (command === \"check\") return check(rest);\n if (command === \"migrate-layout\") return migrate(rest);\n if (command === \"import\") return importCommand(rest);\n // The first word is always a command, so that a directory named `init` is not one.\n if (command === undefined || command === \"-h\" || command === \"--help\") return void process.stdout.write(`${USAGE}\\n`);\n if (command === \"-v\" || command === \"--version\") return void process.stdout.write(`${VERSION}\\n`);\n if (command !== \"start\") fail(`Unknown command \"${command}\". To open the editor: npx @micacms/editor start`);\n\n const options = parse(rest);\n const content = contentRoot(options, process.env);\n\n if (!existsSync(content)) {\n report(`There is no content directory at ${content}.`, [\n \"Mica edits the directory your Astro project publishes content into —\",\n \"the `root` in your `defineSite({ root: \\\"./content\\\" })`.\",\n \"\",\n \" npx @micacms/editor start ./path/to/content\",\n \"\",\n \"If this is a new project, create the directory and Mica writes the first\",\n \"entry into it.\",\n ]);\n process.exit(64); // EX_USAGE\n }\n\n // A far more useful message than the server's, because here we know the\n // project is on this machine and can name the command that fixes it.\n if (!existsSync(path.join(content, MANIFEST_PATH))) {\n report(`There is no content model at ${path.join(content, MANIFEST_PATH)}.`, [\n \"Mica ships without one: your Astro project defines the model, and building\",\n \"the project writes it there. Run this in the project, then start Mica again:\",\n \"\",\n \" npx astro sync # or build, or start the dev server once\",\n \"\",\n \"If you have not defined a model yet, run `npx @micacms/editor init`, or see\",\n \"https://micacms.com/docs/getting-started/.\",\n ]);\n process.exit(78); // EX_CONFIG\n }\n\n Object.assign(process.env, serverEnvironment(options, process.env, content));\n\n // The server is imported rather than spawned: one process, one signal\n // handler, and a stack trace that points at the real thing.\n await import(\"./index.js\");\n}\n\nfunction init(argv: string[]): void {\n if (argv.length > 0) fail(`init takes no options; it works in the current directory.`);\n try {\n process.stdout.write(formatInit(initProject(process.cwd())));\n } catch (error) {\n if (!(error instanceof InitError)) throw error;\n report(error.message, []);\n process.exit(64); // EX_USAGE\n }\n}\n\nasync function check(argv: string[]): Promise<void> {\n const root = contentRoot(parse(argv), process.env);\n const result = await checkContent(root);\n process.stdout.write(formatReport(result, root));\n if (result.problems.some((problem) => problem.level === \"error\")) process.exit(1);\n}\n\nasync function migrate(argv: string[]): Promise<void> {\n const result = await migrateLayout(contentRoot(parse(argv), process.env));\n process.stdout.write(formatMigration(result));\n if (result.conflicts.length > 0) process.exit(1);\n}\n\nasync function importCommand(argv: string[]): Promise<void> {\n // `astro sync` and `astro build` write the store into node_modules; `astro dev` into .astro.\n const found = [\"node_modules/.astro/data-store.json\", \".astro/data-store.json\"].find((file) => existsSync(file));\n const options = { store: found ?? \"node_modules/.astro/data-store.json\", map: {} as Record<string, string>, order: {} as Record<string, string>, only: [] as string[], content: undefined as string | undefined };\n for (let index = 0; index < argv.length; index++) {\n const flag = argv[index]!;\n const value = (): string => {\n const next = argv[++index];\n if (next === undefined || next.startsWith(\"-\")) fail(`${flag} needs a value.`);\n return next!;\n };\n const pair = (text: string): [string, string] => {\n const at = text.indexOf(\"=\");\n if (at < 1 || at === text.length - 1) fail(`${flag} takes name=value, for example team.image.alt=alt.`);\n return [text.slice(0, at), text.slice(at + 1)];\n };\n if (flag === \"--from\") options.store = value();\n else if (flag === \"--content\" || flag === \"-c\") options.content = value();\n else if (flag === \"--map\") { const [to, from] = pair(value()); options.map[to] = from; }\n else if (flag === \"--order\") { const [name, field] = pair(value()); options.order[name] = field; }\n else if (flag.startsWith(\"-\")) fail(`Unknown option \"${flag}\".`);\n else options.only.push(flag);\n }\n try {\n const result = await importCollections({\n root: contentRoot({ content: options.content }, process.env),\n project: process.cwd(),\n store: path.resolve(options.store),\n map: options.map,\n order: options.order,\n only: options.only,\n });\n process.stdout.write(formatImport(result));\n } catch (error) {\n if (!(error instanceof ImportError)) throw error;\n report(error.message, []);\n process.exit(1);\n }\n}\n\n// Only when this file *is* the command. Importing it, as the tests do, must\n// not start a server.\n//\n// `realpathSync` matters: npm and npx run this file through a symlink in\n// `node_modules/.bin`, and Node resolves `import.meta.url` to the symlink's\n// real target. Comparing against the raw `argv[1]` never matched there, so\n// `npx @micacms/editor` silently did nothing.\nif (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {\n main().catch((error: unknown) => {\n process.stderr.write(`\\n${error instanceof Error ? error.stack : String(error)}\\n`);\n process.exit(1);\n });\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the flattened format of `devalue`, which Astro uses for
|
|
3
|
+
* `.astro/data-store.json`: a JSON array whose first item is the root, and in
|
|
4
|
+
* which every other value refers to items by their index. Only what Astro
|
|
5
|
+
* writes is read: objects, arrays, Map, Set, Date and the special numbers.
|
|
6
|
+
*/
|
|
7
|
+
export declare function unflatten(flat: unknown[]): unknown;
|
|
8
|
+
//# sourceMappingURL=devalue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devalue.d.ts","sourceRoot":"","sources":["../src/devalue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAiDlD"}
|
package/dist/devalue.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the flattened format of `devalue`, which Astro uses for
|
|
3
|
+
* `.astro/data-store.json`: a JSON array whose first item is the root, and in
|
|
4
|
+
* which every other value refers to items by their index. Only what Astro
|
|
5
|
+
* writes is read: objects, arrays, Map, Set, Date and the special numbers.
|
|
6
|
+
*/
|
|
7
|
+
const SPECIAL = {
|
|
8
|
+
[-1]: undefined,
|
|
9
|
+
[-2]: undefined, // a hole in an array
|
|
10
|
+
[-3]: Number.NaN,
|
|
11
|
+
[-4]: Number.POSITIVE_INFINITY,
|
|
12
|
+
[-5]: Number.NEGATIVE_INFINITY,
|
|
13
|
+
[-6]: -0,
|
|
14
|
+
};
|
|
15
|
+
export function unflatten(flat) {
|
|
16
|
+
const done = new Map();
|
|
17
|
+
const value = (index) => {
|
|
18
|
+
if (index < 0)
|
|
19
|
+
return SPECIAL[index];
|
|
20
|
+
if (done.has(index))
|
|
21
|
+
return done.get(index);
|
|
22
|
+
const item = flat[index];
|
|
23
|
+
if (Array.isArray(item)) {
|
|
24
|
+
if (typeof item[0] === "string") {
|
|
25
|
+
const [type, ...rest] = item;
|
|
26
|
+
if (type === "Date")
|
|
27
|
+
return keep(index, new Date(rest[0]));
|
|
28
|
+
if (type === "Map") {
|
|
29
|
+
const map = keep(index, new Map());
|
|
30
|
+
for (let i = 0; i < rest.length; i += 2)
|
|
31
|
+
map.set(value(rest[i]), value(rest[i + 1]));
|
|
32
|
+
return map;
|
|
33
|
+
}
|
|
34
|
+
if (type === "Set") {
|
|
35
|
+
const set = keep(index, new Set());
|
|
36
|
+
for (const entry of rest)
|
|
37
|
+
set.add(value(entry));
|
|
38
|
+
return set;
|
|
39
|
+
}
|
|
40
|
+
if (type === "null") {
|
|
41
|
+
const object = keep(index, Object.create(null));
|
|
42
|
+
for (let i = 0; i < rest.length; i += 2)
|
|
43
|
+
object[rest[i]] = value(rest[i + 1]);
|
|
44
|
+
return object;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`The data store holds a value of type "${type}", which this version of Mica cannot read.`);
|
|
47
|
+
}
|
|
48
|
+
const array = keep(index, []);
|
|
49
|
+
for (const entry of item)
|
|
50
|
+
array.push(value(entry));
|
|
51
|
+
return array;
|
|
52
|
+
}
|
|
53
|
+
if (item !== null && typeof item === "object") {
|
|
54
|
+
const object = keep(index, {});
|
|
55
|
+
for (const [key, entry] of Object.entries(item))
|
|
56
|
+
object[key] = value(entry);
|
|
57
|
+
return object;
|
|
58
|
+
}
|
|
59
|
+
return keep(index, item);
|
|
60
|
+
};
|
|
61
|
+
function keep(index, result) {
|
|
62
|
+
done.set(index, result);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
return value(0);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=devalue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devalue.js","sourceRoot":"","sources":["../src/devalue.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reads the flattened format of `devalue`, which Astro uses for\n * `.astro/data-store.json`: a JSON array whose first item is the root, and in\n * which every other value refers to items by their index. Only what Astro\n * writes is read: objects, arrays, Map, Set, Date and the special numbers.\n */\n\nconst SPECIAL: Record<number, unknown> = {\n [-1]: undefined,\n [-2]: undefined, // a hole in an array\n [-3]: Number.NaN,\n [-4]: Number.POSITIVE_INFINITY,\n [-5]: Number.NEGATIVE_INFINITY,\n [-6]: -0,\n};\n\nexport function unflatten(flat: unknown[]): unknown {\n const done = new Map<number, unknown>();\n\n const value = (index: number): unknown => {\n if (index < 0) return SPECIAL[index];\n if (done.has(index)) return done.get(index);\n const item = flat[index];\n\n if (Array.isArray(item)) {\n if (typeof item[0] === \"string\") {\n const [type, ...rest] = item as [string, ...unknown[]];\n if (type === \"Date\") return keep(index, new Date(rest[0] as string));\n if (type === \"Map\") {\n const map = keep(index, new Map<unknown, unknown>());\n for (let i = 0; i < rest.length; i += 2) map.set(value(rest[i] as number), value(rest[i + 1] as number));\n return map;\n }\n if (type === \"Set\") {\n const set = keep(index, new Set<unknown>());\n for (const entry of rest) set.add(value(entry as number));\n return set;\n }\n if (type === \"null\") {\n const object = keep(index, Object.create(null) as Record<string, unknown>);\n for (let i = 0; i < rest.length; i += 2) object[rest[i] as string] = value(rest[i + 1] as number);\n return object;\n }\n throw new Error(`The data store holds a value of type \"${type}\", which this version of Mica cannot read.`);\n }\n const array = keep(index, [] as unknown[]);\n for (const entry of item as number[]) array.push(value(entry));\n return array;\n }\n\n if (item !== null && typeof item === \"object\") {\n const object = keep(index, {} as Record<string, unknown>);\n for (const [key, entry] of Object.entries(item as Record<string, number>)) object[key] = value(entry);\n return object;\n }\n\n return keep(index, item);\n };\n\n function keep<T>(index: number, result: T): T {\n done.set(index, result);\n return result;\n }\n\n return value(0);\n}\n"]}
|
package/dist/import.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mica import`: moves the entries of Astro content collections into Mica.
|
|
3
|
+
*
|
|
4
|
+
* It reads `.astro/data-store.json`, where Astro keeps every entry after its
|
|
5
|
+
* own loader and schema ran, so one importer covers `glob()`, `file()`,
|
|
6
|
+
* Markdown, MDX frontmatter, JSON and YAML alike. Each field of the Mica model
|
|
7
|
+
* takes the field with the same name, unless `--map` names another; values are
|
|
8
|
+
* converted to the stored form and written with the editor's own serializer.
|
|
9
|
+
*/
|
|
10
|
+
/** The `--map` value for the Markdown body. Not `$body`: a shell would expand it. */
|
|
11
|
+
export declare const BODY = "@body";
|
|
12
|
+
export interface ImportOptions {
|
|
13
|
+
/** The Mica content root. */
|
|
14
|
+
root: string;
|
|
15
|
+
/** The Astro project, which the entries' file paths are relative to. */
|
|
16
|
+
project: string;
|
|
17
|
+
/** Astro's data store from before Mica replaced the collections: `node_modules/.astro/data-store.json`. */
|
|
18
|
+
store: string;
|
|
19
|
+
/** `collection.targetField` (dots for fields in an object, and `.alt`) → source key; `@body` is the Markdown body. Not inside repeater rows. */
|
|
20
|
+
map?: Record<string, string>;
|
|
21
|
+
/** Collection → the number field that gives the initial manual order. */
|
|
22
|
+
order?: Record<string, string>;
|
|
23
|
+
/** Only these collections and singletons; all that match by name when empty. */
|
|
24
|
+
only?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ImportResult {
|
|
27
|
+
imported: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
entries: number;
|
|
30
|
+
}>;
|
|
31
|
+
problems: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare class ImportError extends Error {
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function importCollections(options: ImportOptions): Promise<ImportResult>;
|
|
37
|
+
/** The result as a person or an agent reads it. */
|
|
38
|
+
export declare function formatImport(result: ImportResult): string;
|
|
39
|
+
//# sourceMappingURL=import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../src/import.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAqBH,qFAAqF;AACrF,eAAO,MAAM,IAAI,UAAU,CAAC;AAK5B,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,2GAA2G;IAC3G,KAAK,EAAE,MAAM,CAAC;IACd,gJAAgJ;IAChJ,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AASD,qBAAa,WAAY,SAAQ,KAAK;IAC3B,IAAI,SAAiB;CAC/B;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAQrF;AA8PD,mDAAmD;AACnD,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAKzD"}
|
package/dist/import.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mica import`: moves the entries of Astro content collections into Mica.
|
|
3
|
+
*
|
|
4
|
+
* It reads `.astro/data-store.json`, where Astro keeps every entry after its
|
|
5
|
+
* own loader and schema ran, so one importer covers `glob()`, `file()`,
|
|
6
|
+
* Markdown, MDX frontmatter, JSON and YAML alike. Each field of the Mica model
|
|
7
|
+
* takes the field with the same name, unless `--map` names another; values are
|
|
8
|
+
* converted to the stored form and written with the editor's own serializer.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { copyFile, mkdir, writeFile } from "node:fs/promises";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { collectionEntryPath, entryFiles, serializeEntry, singletonPath, slugify, validateFields, } from "@micacms/core";
|
|
14
|
+
import { within } from "@micacms/core/node";
|
|
15
|
+
import { contentFiles, loadSchema } from "@micacms/runtime";
|
|
16
|
+
import { unflatten } from "./devalue.js";
|
|
17
|
+
/** The `--map` value for the Markdown body. Not `$body`: a shell would expand it. */
|
|
18
|
+
export const BODY = "@body";
|
|
19
|
+
/** Astro's marker for a path that `image()` resolves against the entry's file. */
|
|
20
|
+
const IMAGE_MARKER = "__ASTRO_IMAGE_";
|
|
21
|
+
export class ImportError extends Error {
|
|
22
|
+
name = "ImportError";
|
|
23
|
+
}
|
|
24
|
+
export async function importCollections(options) {
|
|
25
|
+
try {
|
|
26
|
+
return await importAll(options);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// Any refusal reads as one, not as a stack trace.
|
|
30
|
+
if (error instanceof ImportError)
|
|
31
|
+
throw error;
|
|
32
|
+
throw new ImportError(error.message, { cause: error });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function importAll(options) {
|
|
36
|
+
const site = await loadSchema(contentFiles({ root: options.root }));
|
|
37
|
+
const astro = readStore(options.store);
|
|
38
|
+
const result = { imported: [], problems: [] };
|
|
39
|
+
const map = options.map ?? {};
|
|
40
|
+
const names = [...Object.keys(site.collections), ...Object.keys(site.singletons)].filter((name) => astro.has(name) && (!options.only?.length || options.only.includes(name)));
|
|
41
|
+
for (const name of options.only ?? []) {
|
|
42
|
+
if (!astro.has(name))
|
|
43
|
+
result.problems.push(`${name}: the data store has no collection with this name.`);
|
|
44
|
+
}
|
|
45
|
+
if (names.length === 0)
|
|
46
|
+
throw new ImportError("No Astro collection has the name of a Mica collection or singleton.");
|
|
47
|
+
// Before the first write, so that a refusal leaves nothing half done.
|
|
48
|
+
for (const name of names) {
|
|
49
|
+
const fields = (site.singletons[name] ?? site.collections[name]).fields;
|
|
50
|
+
const target = site.singletons[name] ? singletonPath(fields, name) : name;
|
|
51
|
+
if (existsSync(within(options.root, target))) {
|
|
52
|
+
throw new ImportError(`${target} exists in the content root. Delete it to import "${name}" again.`);
|
|
53
|
+
}
|
|
54
|
+
if (!site.singletons[name]) {
|
|
55
|
+
const empty = astro.get(name).find((entry) => slugify(entry.id.replace(/\//g, "-")) === "");
|
|
56
|
+
if (empty)
|
|
57
|
+
throw new ImportError(`${name}: the id "${empty.id}" gives no slug. Rename the entry first.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const name of names) {
|
|
61
|
+
const entries = astro.get(name);
|
|
62
|
+
const singleton = site.singletons[name];
|
|
63
|
+
const fields = (singleton ?? site.collections[name]).fields;
|
|
64
|
+
if (singleton && entries.length !== 1) {
|
|
65
|
+
result.problems.push(`${name}: a singleton takes one entry, and the collection has ${entries.length}. Not imported.`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const used = new Set();
|
|
69
|
+
let bodyTaken = false;
|
|
70
|
+
const slugs = new Map();
|
|
71
|
+
const written = [];
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
const slug = slugify(entry.id.replace(/\//g, "-"));
|
|
74
|
+
if (!singleton && slugs.has(slug)) {
|
|
75
|
+
result.problems.push(`${name}: "${entry.id}" and "${slugs.get(slug)}" both become "${slug}". Not imported: "${entry.id}".`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
slugs.set(slug, entry.id);
|
|
79
|
+
const base = singleton ? singletonPath(fields, name) : collectionEntryPath(fields, name, slug);
|
|
80
|
+
const context = { options, map, name, entry, base, slug, used, problems: result.problems, copies: [], body: false };
|
|
81
|
+
const data = convertFields(fields, entry.data, "", context, singleton ? undefined : slug);
|
|
82
|
+
bodyTaken ||= context.body;
|
|
83
|
+
const check = validateFields(fields, data);
|
|
84
|
+
if (!check.ok) {
|
|
85
|
+
for (const issue of check.issues)
|
|
86
|
+
result.problems.push(`${name}/${slug}: ${issue.path}: ${issue.message}`);
|
|
87
|
+
}
|
|
88
|
+
for (const [file, contents] of Object.entries(entryFiles(fields, base, serializeEntry(fields, data)))) {
|
|
89
|
+
await mkdir(path.dirname(within(options.root, file)), { recursive: true });
|
|
90
|
+
await writeFile(within(options.root, file), contents);
|
|
91
|
+
}
|
|
92
|
+
for (const { from, to } of context.copies) {
|
|
93
|
+
await mkdir(path.dirname(within(options.root, to)), { recursive: true });
|
|
94
|
+
await copyFile(from, within(options.root, to));
|
|
95
|
+
}
|
|
96
|
+
written.push({ slug, source: entry });
|
|
97
|
+
}
|
|
98
|
+
const orderField = options.order?.[name];
|
|
99
|
+
if (orderField) {
|
|
100
|
+
const def = site.collections[name];
|
|
101
|
+
if (!def?.sortable)
|
|
102
|
+
result.problems.push(`${name}: --order needs a collection with \`sortable: true\`.`);
|
|
103
|
+
else {
|
|
104
|
+
const position = (item) => {
|
|
105
|
+
const value = Number(item.source.data[orderField]);
|
|
106
|
+
return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;
|
|
107
|
+
};
|
|
108
|
+
const order = [...written].sort((a, b) => position(a) - position(b)).map((item) => item.slug);
|
|
109
|
+
await writeFile(within(options.root, `${name}/_order.json`), `${JSON.stringify(order, null, 2)}\n`);
|
|
110
|
+
used.add(orderField);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!bodyTaken && entries.some((entry) => entry.body?.trim())) {
|
|
114
|
+
result.problems.push(`${name}: not imported: the Markdown body. The model has no rich-text field \`body\`; map one with --map ${name}.<field>=@body.`);
|
|
115
|
+
}
|
|
116
|
+
const left = new Set(entries.flatMap((entry) => Object.keys(entry.data)).filter((key) => !used.has(key)));
|
|
117
|
+
if (left.size > 0)
|
|
118
|
+
result.problems.push(`${name}: not imported: ${[...left].join(", ")}. Map them with --map, or leave them out.`);
|
|
119
|
+
result.imported.push({ name, entries: written.length });
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function convertFields(fields, source, prefix, context, slug) {
|
|
124
|
+
const data = {};
|
|
125
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
126
|
+
const at = prefix ? `${prefix}.${key}` : key;
|
|
127
|
+
if (field.kind === "slug" && prefix === "" && slug !== undefined) {
|
|
128
|
+
data[key] = slug;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
data[key] = convert(field, read(source, at, key, context), at, context);
|
|
132
|
+
}
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
/** The source value for a target field: mapped, else the key of the same name. */
|
|
136
|
+
function read(source, at, key, context) {
|
|
137
|
+
const mapped = context.map[`${context.name}.${at}`];
|
|
138
|
+
if (mapped === BODY) {
|
|
139
|
+
context.body = true;
|
|
140
|
+
return context.entry.body?.trim() || null;
|
|
141
|
+
}
|
|
142
|
+
if (mapped !== undefined) {
|
|
143
|
+
context.used.add(mapped.split(".")[0]);
|
|
144
|
+
return mapped.split(".").reduce((value, part) => (isRecord(value) ? value[part] : undefined), context.entry.data);
|
|
145
|
+
}
|
|
146
|
+
if (!at.includes("."))
|
|
147
|
+
context.used.add(key);
|
|
148
|
+
return source[key];
|
|
149
|
+
}
|
|
150
|
+
function convert(field, value, at, context) {
|
|
151
|
+
const where = `${context.name}/${context.slug}: ${at}`;
|
|
152
|
+
switch (field.kind) {
|
|
153
|
+
case "richText":
|
|
154
|
+
// The body goes to the rich-text field named `body`, unless a map says otherwise.
|
|
155
|
+
if (value == null && at === "body" && !(`${context.name}.body` in context.map) && context.entry.body?.trim()) {
|
|
156
|
+
context.body = true;
|
|
157
|
+
return context.entry.body.trim();
|
|
158
|
+
}
|
|
159
|
+
return value ?? null;
|
|
160
|
+
case "date":
|
|
161
|
+
return toDate(value, where, context, field.includeTime === true);
|
|
162
|
+
case "relation": {
|
|
163
|
+
const one = (item) => (isRecord(item) ? slugify(String(item["id"] ?? "")) : typeof item === "string" ? slugify(item) : null);
|
|
164
|
+
return field.multiple ? (Array.isArray(value) ? value.map(one).filter(Boolean) : []) : one(value);
|
|
165
|
+
}
|
|
166
|
+
case "media": {
|
|
167
|
+
const one = (item, index) => {
|
|
168
|
+
const stored = copyMedia(item, `${where}${index === undefined ? "" : `.${index}`}`, context);
|
|
169
|
+
if (stored === null || !field.alt)
|
|
170
|
+
return stored;
|
|
171
|
+
const alt = context.map[`${context.name}.${at}.alt`];
|
|
172
|
+
if (alt !== undefined)
|
|
173
|
+
context.used.add(alt.split(".")[0]);
|
|
174
|
+
const text = alt === undefined ? null : alt.split(".").reduce((v, p) => (isRecord(v) ? v[p] : undefined), context.entry.data);
|
|
175
|
+
return { path: stored, alt: typeof text === "string" ? text : null };
|
|
176
|
+
};
|
|
177
|
+
if (field.multiple)
|
|
178
|
+
return Array.isArray(value) ? value.map((item, index) => one(item, index)).filter((item) => item !== null) : [];
|
|
179
|
+
return one(value);
|
|
180
|
+
}
|
|
181
|
+
case "object": {
|
|
182
|
+
// A map can fill an object from flat keys, for example `blog.seo.title=metaTitle`.
|
|
183
|
+
const mapped = Object.keys(context.map).some((key) => key.startsWith(`${context.name}.${at}.`));
|
|
184
|
+
return isRecord(value) || mapped ? convertFields(field.fields, isRecord(value) ? value : {}, at, context) : null;
|
|
185
|
+
}
|
|
186
|
+
case "repeater":
|
|
187
|
+
return Array.isArray(value)
|
|
188
|
+
? value.map((row, index) => (isRecord(row) ? convertFields(field.fields, row, `${at}.${index}`, { ...context, map: {} }) : {}))
|
|
189
|
+
: [];
|
|
190
|
+
case "select":
|
|
191
|
+
return field.multiple ? (Array.isArray(value) ? value : []) : (value ?? null);
|
|
192
|
+
case "boolean":
|
|
193
|
+
return typeof value === "boolean" ? value : (field.default ?? false);
|
|
194
|
+
case "number":
|
|
195
|
+
return typeof value === "number" ? value : value == null ? null : Number(value);
|
|
196
|
+
default:
|
|
197
|
+
return value instanceof Date ? value.toISOString() : (value ?? null);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A date as `YYYY-MM-DD` in UTC (a local-time date would give the day before
|
|
202
|
+
* east of UTC), or a full ISO timestamp for `date({ includeTime: true })`.
|
|
203
|
+
*/
|
|
204
|
+
function toDate(value, where, context, withTime) {
|
|
205
|
+
if (value == null || value === "")
|
|
206
|
+
return null;
|
|
207
|
+
const parsed = value instanceof Date
|
|
208
|
+
? value
|
|
209
|
+
: typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value)
|
|
210
|
+
? new Date(value.length === 10 ? `${value}T00:00:00Z` : value)
|
|
211
|
+
: new Date(`${String(value)} UTC`);
|
|
212
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
213
|
+
context.problems.push(`${where}: "${String(value)}" is not a date.`);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
return withTime ? parsed.toISOString() : parsed.toISOString().slice(0, 10);
|
|
217
|
+
}
|
|
218
|
+
/** Copies a media file into the entry's `media/` folder, and gives the stored path. */
|
|
219
|
+
function copyMedia(value, where, context) {
|
|
220
|
+
if (typeof value !== "string" || value === "")
|
|
221
|
+
return null;
|
|
222
|
+
const raw = value.startsWith(IMAGE_MARKER) ? value.slice(IMAGE_MARKER.length) : value;
|
|
223
|
+
if (/^[a-z]+:\/\//i.test(raw)) {
|
|
224
|
+
context.problems.push(`${where}: "${raw}" is a URL. Download the file and add it in the editor.`);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const from = raw.startsWith("/")
|
|
228
|
+
? path.join(context.options.project, raw)
|
|
229
|
+
: path.resolve(context.options.project, path.dirname(context.entry.filePath ?? "."), raw);
|
|
230
|
+
if (!existsSync(from)) {
|
|
231
|
+
context.problems.push(`${where}: the file "${raw}" does not exist.`);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const taken = new Set(context.copies.map((copy) => path.posix.basename(copy.to)));
|
|
235
|
+
let name = path.basename(from);
|
|
236
|
+
for (let n = 2; taken.has(name); n++)
|
|
237
|
+
name = `${path.parse(from).name}-${n}${path.extname(from)}`;
|
|
238
|
+
context.copies.push({ from, to: `${context.base}/media/${name}` });
|
|
239
|
+
return `media/${name}`;
|
|
240
|
+
}
|
|
241
|
+
/** Collection name → its entries, from Astro's data store. */
|
|
242
|
+
function readStore(file) {
|
|
243
|
+
if (!existsSync(file)) {
|
|
244
|
+
throw new ImportError(`There is no data store at ${file}. Run \`npx astro sync\` before Mica replaces the collections, and keep a copy.`);
|
|
245
|
+
}
|
|
246
|
+
const root = unflatten(JSON.parse(readFileSync(file, "utf8")));
|
|
247
|
+
if (!(root instanceof Map))
|
|
248
|
+
throw new ImportError(`${file} is not an Astro data store.`);
|
|
249
|
+
const collections = new Map();
|
|
250
|
+
for (const [name, entries] of root) {
|
|
251
|
+
if (typeof name !== "string" || name.includes("::") || !(entries instanceof Map))
|
|
252
|
+
continue;
|
|
253
|
+
collections.set(name, [...entries.values()]);
|
|
254
|
+
}
|
|
255
|
+
return collections;
|
|
256
|
+
}
|
|
257
|
+
function isRecord(value) {
|
|
258
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
259
|
+
}
|
|
260
|
+
/** The result as a person or an agent reads it. */
|
|
261
|
+
export function formatImport(result) {
|
|
262
|
+
const lines = result.imported.map(({ name, entries }) => ` imported ${name}: ${entries} ${entries === 1 ? "entry" : "entries"}`);
|
|
263
|
+
for (const problem of result.problems)
|
|
264
|
+
lines.push(` check ${problem}`);
|
|
265
|
+
lines.push("", "Next: run `npx mica check`, and compare the website with the version before.");
|
|
266
|
+
return `${lines.join("\n")}\n`;
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.js","sourceRoot":"","sources":["../src/import.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `mica import`: moves the entries of Astro content collections into Mica.\n *\n * It reads `.astro/data-store.json`, where Astro keeps every entry after its\n * own loader and schema ran, so one importer covers `glob()`, `file()`,\n * Markdown, MDX frontmatter, JSON and YAML alike. Each field of the Mica model\n * takes the field with the same name, unless `--map` names another; values are\n * converted to the stored form and written with the editor's own serializer.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { copyFile, mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport {\n collectionEntryPath,\n entryFiles,\n serializeEntry,\n singletonPath,\n slugify,\n validateFields,\n type AnyField,\n type FieldMap,\n} from \"@micacms/core\";\nimport { within } from \"@micacms/core/node\";\nimport { contentFiles, loadSchema } from \"@micacms/runtime\";\n\nimport { unflatten } from \"./devalue.js\";\n\n/** The `--map` value for the Markdown body. Not `$body`: a shell would expand it. */\nexport const BODY = \"@body\";\n\n/** Astro's marker for a path that `image()` resolves against the entry's file. */\nconst IMAGE_MARKER = \"__ASTRO_IMAGE_\";\n\nexport interface ImportOptions {\n /** The Mica content root. */\n root: string;\n /** The Astro project, which the entries' file paths are relative to. */\n project: string;\n /** Astro's data store from before Mica replaced the collections: `node_modules/.astro/data-store.json`. */\n store: string;\n /** `collection.targetField` (dots for fields in an object, and `.alt`) → source key; `@body` is the Markdown body. Not inside repeater rows. */\n map?: Record<string, string>;\n /** Collection → the number field that gives the initial manual order. */\n order?: Record<string, string>;\n /** Only these collections and singletons; all that match by name when empty. */\n only?: string[];\n}\n\nexport interface ImportResult {\n imported: Array<{ name: string; entries: number }>;\n problems: string[];\n}\n\ninterface StoredEntry {\n id: string;\n data: Record<string, unknown>;\n body?: string;\n filePath?: string;\n}\n\nexport class ImportError extends Error {\n override name = \"ImportError\";\n}\n\nexport async function importCollections(options: ImportOptions): Promise<ImportResult> {\n try {\n return await importAll(options);\n } catch (error) {\n // Any refusal reads as one, not as a stack trace.\n if (error instanceof ImportError) throw error;\n throw new ImportError((error as Error).message, { cause: error });\n }\n}\n\nasync function importAll(options: ImportOptions): Promise<ImportResult> {\n const site = await loadSchema(contentFiles({ root: options.root }));\n const astro = readStore(options.store);\n const result: ImportResult = { imported: [], problems: [] };\n const map = options.map ?? {};\n\n const names = [...Object.keys(site.collections), ...Object.keys(site.singletons)].filter(\n (name) => astro.has(name) && (!options.only?.length || options.only.includes(name)),\n );\n for (const name of options.only ?? []) {\n if (!astro.has(name)) result.problems.push(`${name}: the data store has no collection with this name.`);\n }\n if (names.length === 0) throw new ImportError(\"No Astro collection has the name of a Mica collection or singleton.\");\n\n // Before the first write, so that a refusal leaves nothing half done.\n for (const name of names) {\n const fields = (site.singletons[name] ?? site.collections[name]!).fields;\n const target = site.singletons[name] ? singletonPath(fields, name) : name;\n if (existsSync(within(options.root, target))) {\n throw new ImportError(`${target} exists in the content root. Delete it to import \"${name}\" again.`);\n }\n if (!site.singletons[name]) {\n const empty = astro.get(name)!.find((entry) => slugify(entry.id.replace(/\\//g, \"-\")) === \"\");\n if (empty) throw new ImportError(`${name}: the id \"${empty.id}\" gives no slug. Rename the entry first.`);\n }\n }\n\n for (const name of names) {\n const entries = astro.get(name)!;\n const singleton = site.singletons[name];\n const fields = (singleton ?? site.collections[name]!).fields;\n if (singleton && entries.length !== 1) {\n result.problems.push(`${name}: a singleton takes one entry, and the collection has ${entries.length}. Not imported.`);\n continue;\n }\n\n const used = new Set<string>();\n let bodyTaken = false;\n const slugs = new Map<string, string>();\n const written: Array<{ slug: string; source: StoredEntry }> = [];\n\n for (const entry of entries) {\n const slug = slugify(entry.id.replace(/\\//g, \"-\"));\n if (!singleton && slugs.has(slug)) {\n result.problems.push(`${name}: \"${entry.id}\" and \"${slugs.get(slug)}\" both become \"${slug}\". Not imported: \"${entry.id}\".`);\n continue;\n }\n slugs.set(slug, entry.id);\n\n const base = singleton ? singletonPath(fields, name) : collectionEntryPath(fields, name, slug);\n const context: Context = { options, map, name, entry, base, slug, used, problems: result.problems, copies: [], body: false };\n const data = convertFields(fields, entry.data, \"\", context, singleton ? undefined : slug);\n bodyTaken ||= context.body;\n\n const check = validateFields(fields, data);\n if (!check.ok) {\n for (const issue of check.issues) result.problems.push(`${name}/${slug}: ${issue.path}: ${issue.message}`);\n }\n for (const [file, contents] of Object.entries(entryFiles(fields, base, serializeEntry(fields, data)))) {\n await mkdir(path.dirname(within(options.root, file)), { recursive: true });\n await writeFile(within(options.root, file), contents);\n }\n for (const { from, to } of context.copies) {\n await mkdir(path.dirname(within(options.root, to)), { recursive: true });\n await copyFile(from, within(options.root, to));\n }\n written.push({ slug, source: entry });\n }\n\n const orderField = options.order?.[name];\n if (orderField) {\n const def = site.collections[name];\n if (!def?.sortable) result.problems.push(`${name}: --order needs a collection with \\`sortable: true\\`.`);\n else {\n const position = (item: { source: StoredEntry }) => {\n const value = Number(item.source.data[orderField]);\n return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;\n };\n const order = [...written].sort((a, b) => position(a) - position(b)).map((item) => item.slug);\n await writeFile(within(options.root, `${name}/_order.json`), `${JSON.stringify(order, null, 2)}\\n`);\n used.add(orderField);\n }\n }\n\n if (!bodyTaken && entries.some((entry) => entry.body?.trim())) {\n result.problems.push(`${name}: not imported: the Markdown body. The model has no rich-text field \\`body\\`; map one with --map ${name}.<field>=@body.`);\n }\n const left = new Set(entries.flatMap((entry) => Object.keys(entry.data)).filter((key) => !used.has(key)));\n if (left.size > 0) result.problems.push(`${name}: not imported: ${[...left].join(\", \")}. Map them with --map, or leave them out.`);\n result.imported.push({ name, entries: written.length });\n }\n\n return result;\n}\n\ninterface Context {\n options: ImportOptions;\n map: Record<string, string>;\n name: string;\n entry: StoredEntry;\n base: string;\n slug: string;\n /** Top-level source keys that some field took. */\n used: Set<string>;\n problems: string[];\n copies: Array<{ from: string; to: string }>;\n /** Whether a field took the Markdown body. */\n body: boolean;\n}\n\nfunction convertFields(\n fields: FieldMap,\n source: Record<string, unknown>,\n prefix: string,\n context: Context,\n slug?: string,\n): Record<string, unknown> {\n const data: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(fields)) {\n const at = prefix ? `${prefix}.${key}` : key;\n if (field.kind === \"slug\" && prefix === \"\" && slug !== undefined) {\n data[key] = slug;\n continue;\n }\n data[key] = convert(field, read(source, at, key, context), at, context);\n }\n return data;\n}\n\n/** The source value for a target field: mapped, else the key of the same name. */\nfunction read(source: Record<string, unknown>, at: string, key: string, context: Context): unknown {\n const mapped = context.map[`${context.name}.${at}`];\n if (mapped === BODY) {\n context.body = true;\n return context.entry.body?.trim() || null;\n }\n if (mapped !== undefined) {\n context.used.add(mapped.split(\".\")[0]!);\n return mapped.split(\".\").reduce<unknown>((value, part) => (isRecord(value) ? value[part] : undefined), context.entry.data);\n }\n if (!at.includes(\".\")) context.used.add(key);\n return source[key];\n}\n\nfunction convert(field: AnyField, value: unknown, at: string, context: Context): unknown {\n const where = `${context.name}/${context.slug}: ${at}`;\n switch (field.kind) {\n case \"richText\":\n // The body goes to the rich-text field named `body`, unless a map says otherwise.\n if (value == null && at === \"body\" && !(`${context.name}.body` in context.map) && context.entry.body?.trim()) {\n context.body = true;\n return context.entry.body.trim();\n }\n return value ?? null;\n case \"date\":\n return toDate(value, where, context, field.includeTime === true);\n case \"relation\": {\n const one = (item: unknown) => (isRecord(item) ? slugify(String(item[\"id\"] ?? \"\")) : typeof item === \"string\" ? slugify(item) : null);\n return field.multiple ? (Array.isArray(value) ? value.map(one).filter(Boolean) : []) : one(value);\n }\n case \"media\": {\n const one = (item: unknown, index?: number) => {\n const stored = copyMedia(item, `${where}${index === undefined ? \"\" : `.${index}`}`, context);\n if (stored === null || !field.alt) return stored;\n const alt = context.map[`${context.name}.${at}.alt`];\n if (alt !== undefined) context.used.add(alt.split(\".\")[0]!);\n const text = alt === undefined ? null : alt.split(\".\").reduce<unknown>((v, p) => (isRecord(v) ? v[p] : undefined), context.entry.data);\n return { path: stored, alt: typeof text === \"string\" ? text : null };\n };\n if (field.multiple) return Array.isArray(value) ? value.map((item, index) => one(item, index)).filter((item) => item !== null) : [];\n return one(value);\n }\n case \"object\": {\n // A map can fill an object from flat keys, for example `blog.seo.title=metaTitle`.\n const mapped = Object.keys(context.map).some((key) => key.startsWith(`${context.name}.${at}.`));\n return isRecord(value) || mapped ? convertFields(field.fields, isRecord(value) ? value : {}, at, context) : null;\n }\n case \"repeater\":\n return Array.isArray(value)\n ? value.map((row, index) => (isRecord(row) ? convertFields(field.fields, row, `${at}.${index}`, { ...context, map: {} }) : {}))\n : [];\n case \"select\":\n return field.multiple ? (Array.isArray(value) ? value : []) : (value ?? null);\n case \"boolean\":\n return typeof value === \"boolean\" ? value : (field.default ?? false);\n case \"number\":\n return typeof value === \"number\" ? value : value == null ? null : Number(value);\n default:\n return value instanceof Date ? value.toISOString() : (value ?? null);\n }\n}\n\n/**\n * A date as `YYYY-MM-DD` in UTC (a local-time date would give the day before\n * east of UTC), or a full ISO timestamp for `date({ includeTime: true })`.\n */\nfunction toDate(value: unknown, where: string, context: Context, withTime: boolean): string | null {\n if (value == null || value === \"\") return null;\n const parsed =\n value instanceof Date\n ? value\n : typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}/.test(value)\n ? new Date(value.length === 10 ? `${value}T00:00:00Z` : value)\n : new Date(`${String(value)} UTC`);\n if (Number.isNaN(parsed.getTime())) {\n context.problems.push(`${where}: \"${String(value)}\" is not a date.`);\n return null;\n }\n return withTime ? parsed.toISOString() : parsed.toISOString().slice(0, 10);\n}\n\n/** Copies a media file into the entry's `media/` folder, and gives the stored path. */\nfunction copyMedia(value: unknown, where: string, context: Context): string | null {\n if (typeof value !== \"string\" || value === \"\") return null;\n const raw = value.startsWith(IMAGE_MARKER) ? value.slice(IMAGE_MARKER.length) : value;\n if (/^[a-z]+:\\/\\//i.test(raw)) {\n context.problems.push(`${where}: \"${raw}\" is a URL. Download the file and add it in the editor.`);\n return null;\n }\n const from = raw.startsWith(\"/\")\n ? path.join(context.options.project, raw)\n : path.resolve(context.options.project, path.dirname(context.entry.filePath ?? \".\"), raw);\n if (!existsSync(from)) {\n context.problems.push(`${where}: the file \"${raw}\" does not exist.`);\n return null;\n }\n const taken = new Set(context.copies.map((copy) => path.posix.basename(copy.to)));\n let name = path.basename(from);\n for (let n = 2; taken.has(name); n++) name = `${path.parse(from).name}-${n}${path.extname(from)}`;\n context.copies.push({ from, to: `${context.base}/media/${name}` });\n return `media/${name}`;\n}\n\n/** Collection name → its entries, from Astro's data store. */\nfunction readStore(file: string): Map<string, StoredEntry[]> {\n if (!existsSync(file)) {\n throw new ImportError(`There is no data store at ${file}. Run \\`npx astro sync\\` before Mica replaces the collections, and keep a copy.`);\n }\n const root = unflatten(JSON.parse(readFileSync(file, \"utf8\")) as unknown[]);\n if (!(root instanceof Map)) throw new ImportError(`${file} is not an Astro data store.`);\n const collections = new Map<string, StoredEntry[]>();\n for (const [name, entries] of root) {\n if (typeof name !== \"string\" || name.includes(\"::\") || !(entries instanceof Map)) continue;\n collections.set(name, [...entries.values()] as StoredEntry[]);\n }\n return collections;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value) && !(value instanceof Date);\n}\n\n/** The result as a person or an agent reads it. */\nexport function formatImport(result: ImportResult): string {\n const lines = result.imported.map(({ name, entries }) => ` imported ${name}: ${entries} ${entries === 1 ? \"entry\" : \"entries\"}`);\n for (const problem of result.problems) lines.push(` check ${problem}`);\n lines.push(\"\", \"Next: run `npx mica check`, and compare the website with the version before.\");\n return `${lines.join(\"\\n\")}\\n`;\n}\n"]}
|
package/dist/init.js
CHANGED
|
@@ -108,14 +108,14 @@ export function initProject(project) {
|
|
|
108
108
|
result.next.push(`Set in "scripts" in package.json:\n\n${scripts.map((line) => ` ${line}`).join(",\n")}`);
|
|
109
109
|
result.next.push(model
|
|
110
110
|
? "Add the website's collections and singletons to the model in src/mica.ts."
|
|
111
|
-
: "Replace the starter model in src/mica.ts (news, settings) with the website's model.", "Write the model to content/.mica/schema.json: npx astro sync", "Check the content: npx mica check", "When the content is in place, open the editor in a browser: npx mica");
|
|
111
|
+
: "Replace the starter model in src/mica.ts (news, settings) with the website's model.", "Write the model to content/.mica/schema.json: npx astro sync", "Check the content: npx mica check", "When the content is in place, open the editor in a browser: npx mica start");
|
|
112
112
|
return result;
|
|
113
113
|
}
|
|
114
|
-
/** `npm run mica` opens the editor; `npm run check` checks the content, and the types when `@astrojs/check` is there. */
|
|
114
|
+
/** `npm run mica` opens the editor (`mica start`); `npm run check` checks the content, and the types when `@astrojs/check` is there. */
|
|
115
115
|
function scriptsToSet(scripts, astroCheck) {
|
|
116
116
|
const missing = [];
|
|
117
117
|
if (!scripts["mica"])
|
|
118
|
-
missing.push(`"mica": "mica"`);
|
|
118
|
+
missing.push(`"mica": "mica start"`);
|
|
119
119
|
// Without @astrojs/check, `astro check` asks to install it, and a script cannot answer.
|
|
120
120
|
const check = scripts["check"];
|
|
121
121
|
if (!check)
|
package/dist/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `mica init` — adds Mica to an Astro project: a starter model and the content\n * collections. It never overwrites a file.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport const MODEL = `import {\n collection,\n date,\n defineSite,\n media,\n richText,\n singleton,\n slug,\n text,\n} from \"@micacms/core\";\n\nexport const site = defineSite({\n root: \"./content\",\n\n collections: {\n news: collection({\n label: \"News\",\n labelSingular: \"Article\",\n fields: {\n title: text({ label: \"Title\", required: true, maxLength: 120, listed: true }),\n slug: slug({ from: \"title\" }),\n publishedOn: date({ label: \"Published on\", listed: true }),\n cover: media({ accept: \"image\", alt: \"required\" }),\n body: richText(),\n },\n }),\n },\n\n singletons: {\n settings: singleton({\n label: \"Website\",\n fields: {\n siteName: text({ label: \"Site name\", required: true }),\n tagline: text({ label: \"Tagline\" }),\n },\n }),\n },\n});\n`;\n\nconst COLLECTIONS = `import { micaCollections } from \"@micacms/astro\";\n\nimport { site } from \"./mica\";\n\nexport const collections = micaCollections(site);\n`;\n\nconst MERGE = ` import { micaCollections } from \"@micacms/astro\";\n import { site } from \"./mica\";\n\n export const collections = { ...micaCollections(site), /* your collections */ };`;\n\nexport interface InitResult {\n /** Files and directories made, relative to the project. */\n created: string[];\n /** Files that already existed and were left alone. */\n kept: string[];\n /** What the person or agent does next. */\n next: string[];\n}\n\nexport class InitError extends Error {\n override name = \"InitError\";\n}\n\n/** Adds Mica to the Astro project in `project`. */\nexport function initProject(project: string): InitResult {\n const manifest = path.join(project, \"package.json\");\n if (!existsSync(manifest)) {\n throw new InitError(`There is no package.json in ${project}. Create an Astro project first: npm create astro@latest`);\n }\n let pkg: {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n };\n try {\n pkg = JSON.parse(readFileSync(manifest, \"utf8\")) as typeof pkg;\n } catch (cause) {\n throw new InitError(`${manifest} is not valid JSON: ${(cause as Error).message}`);\n }\n const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };\n if (!dependencies[\"astro\"]) {\n throw new InitError(`${manifest} has no \"astro\" dependency. Run mica init in an Astro project.`);\n }\n\n const result: InitResult = { created: [], kept: [], next: [] };\n\n const model = existingFile(project, [\"src/mica.ts\", \"src/mica.mts\", \"src/mica.js\", \"src/mica.mjs\"]);\n if (model) result.kept.push(model);\n else write(project, \"src/mica.ts\", MODEL, result);\n\n const config = existingFile(project, [\n \"src/content.config.ts\",\n \"src/content.config.mts\",\n \"src/content.config.js\",\n \"src/content.config.mjs\",\n \"src/content/config.ts\",\n \"src/content/config.mts\",\n \"src/content/config.js\",\n \"src/content/config.mjs\",\n ]);\n if (config) {\n result.kept.push(config);\n if (!readFileSync(path.join(project, config), \"utf8\").includes(\"micaCollections\")) {\n result.next.push(`Add the Mica collections to ${config}:\\n\\n${MERGE}`);\n }\n } else {\n write(project, \"src/content.config.ts\", COLLECTIONS, result);\n }\n\n const missing = [\"@micacms/astro\", \"@micacms/core\"].filter((name) => !dependencies[name]);\n if (missing.length > 0) result.next.push(`Install the packages: npm install ${missing.join(\" \")}`);\n if (!dependencies[\"@micacms/editor\"]) result.next.push(\"Install the editor: npm install --save-dev @micacms/editor\");\n const scripts = scriptsToSet(pkg.scripts ?? {}, Boolean(dependencies[\"@astrojs/check\"]));\n if (scripts.length > 0) result.next.push(`Set in \"scripts\" in package.json:\\n\\n${scripts.map((line) => ` ${line}`).join(\",\\n\")}`);\n result.next.push(\n model\n ? \"Add the website's collections and singletons to the model in src/mica.ts.\"\n : \"Replace the starter model in src/mica.ts (news, settings) with the website's model.\",\n \"Write the model to content/.mica/schema.json: npx astro sync\",\n \"Check the content: npx mica check\",\n \"When the content is in place, open the editor in a browser: npx mica\",\n );\n return result;\n}\n\n/** `npm run mica` opens the editor; `npm run check` checks the content, and the types when `@astrojs/check` is there. */\nfunction scriptsToSet(scripts: Record<string, string>, astroCheck: boolean): string[] {\n const missing: string[] = [];\n if (!scripts[\"mica\"]) missing.push(`\"mica\": \"mica\"`);\n // Without @astrojs/check, `astro check` asks to install it, and a script cannot answer.\n const check = scripts[\"check\"];\n if (!check) missing.push(`\"check\": \"${astroCheck ? \"mica check && astro check\" : \"mica check\"}\"`);\n else if (!check.includes(\"mica check\")) missing.push(`\"check\": ${JSON.stringify(`mica check && ${check}`)}`);\n return missing;\n}\n\nfunction existingFile(project: string, candidates: string[]): string | undefined {\n return candidates.find((candidate) => existsSync(path.join(project, candidate)));\n}\n\nfunction write(project: string, file: string, contents: string, result: InitResult): void {\n mkdirSync(path.dirname(path.join(project, file)), { recursive: true });\n writeFileSync(path.join(project, file), contents, { flag: \"wx\" });\n result.created.push(file);\n}\n\n/** The result as a person or an agent reads it. */\nexport function formatInit(result: InitResult): string {\n const lines: string[] = [];\n for (const file of result.created) lines.push(` created ${file}`);\n for (const file of result.kept) lines.push(` kept ${file}`);\n lines.push(\"\", \"Next:\", \"\");\n result.next.forEach((step, index) => lines.push(` ${index + 1}. ${step}`, ...(step.includes(\"\\n\") ? [\"\"] : [])));\n return `${lines.join(\"\\n\")}\\n`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `mica init` — adds Mica to an Astro project: a starter model and the content\n * collections. It never overwrites a file.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport const MODEL = `import {\n collection,\n date,\n defineSite,\n media,\n richText,\n singleton,\n slug,\n text,\n} from \"@micacms/core\";\n\nexport const site = defineSite({\n root: \"./content\",\n\n collections: {\n news: collection({\n label: \"News\",\n labelSingular: \"Article\",\n fields: {\n title: text({ label: \"Title\", required: true, maxLength: 120, listed: true }),\n slug: slug({ from: \"title\" }),\n publishedOn: date({ label: \"Published on\", listed: true }),\n cover: media({ accept: \"image\", alt: \"required\" }),\n body: richText(),\n },\n }),\n },\n\n singletons: {\n settings: singleton({\n label: \"Website\",\n fields: {\n siteName: text({ label: \"Site name\", required: true }),\n tagline: text({ label: \"Tagline\" }),\n },\n }),\n },\n});\n`;\n\nconst COLLECTIONS = `import { micaCollections } from \"@micacms/astro\";\n\nimport { site } from \"./mica\";\n\nexport const collections = micaCollections(site);\n`;\n\nconst MERGE = ` import { micaCollections } from \"@micacms/astro\";\n import { site } from \"./mica\";\n\n export const collections = { ...micaCollections(site), /* your collections */ };`;\n\nexport interface InitResult {\n /** Files and directories made, relative to the project. */\n created: string[];\n /** Files that already existed and were left alone. */\n kept: string[];\n /** What the person or agent does next. */\n next: string[];\n}\n\nexport class InitError extends Error {\n override name = \"InitError\";\n}\n\n/** Adds Mica to the Astro project in `project`. */\nexport function initProject(project: string): InitResult {\n const manifest = path.join(project, \"package.json\");\n if (!existsSync(manifest)) {\n throw new InitError(`There is no package.json in ${project}. Create an Astro project first: npm create astro@latest`);\n }\n let pkg: {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n };\n try {\n pkg = JSON.parse(readFileSync(manifest, \"utf8\")) as typeof pkg;\n } catch (cause) {\n throw new InitError(`${manifest} is not valid JSON: ${(cause as Error).message}`);\n }\n const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };\n if (!dependencies[\"astro\"]) {\n throw new InitError(`${manifest} has no \"astro\" dependency. Run mica init in an Astro project.`);\n }\n\n const result: InitResult = { created: [], kept: [], next: [] };\n\n const model = existingFile(project, [\"src/mica.ts\", \"src/mica.mts\", \"src/mica.js\", \"src/mica.mjs\"]);\n if (model) result.kept.push(model);\n else write(project, \"src/mica.ts\", MODEL, result);\n\n const config = existingFile(project, [\n \"src/content.config.ts\",\n \"src/content.config.mts\",\n \"src/content.config.js\",\n \"src/content.config.mjs\",\n \"src/content/config.ts\",\n \"src/content/config.mts\",\n \"src/content/config.js\",\n \"src/content/config.mjs\",\n ]);\n if (config) {\n result.kept.push(config);\n if (!readFileSync(path.join(project, config), \"utf8\").includes(\"micaCollections\")) {\n result.next.push(`Add the Mica collections to ${config}:\\n\\n${MERGE}`);\n }\n } else {\n write(project, \"src/content.config.ts\", COLLECTIONS, result);\n }\n\n const missing = [\"@micacms/astro\", \"@micacms/core\"].filter((name) => !dependencies[name]);\n if (missing.length > 0) result.next.push(`Install the packages: npm install ${missing.join(\" \")}`);\n if (!dependencies[\"@micacms/editor\"]) result.next.push(\"Install the editor: npm install --save-dev @micacms/editor\");\n const scripts = scriptsToSet(pkg.scripts ?? {}, Boolean(dependencies[\"@astrojs/check\"]));\n if (scripts.length > 0) result.next.push(`Set in \"scripts\" in package.json:\\n\\n${scripts.map((line) => ` ${line}`).join(\",\\n\")}`);\n result.next.push(\n model\n ? \"Add the website's collections and singletons to the model in src/mica.ts.\"\n : \"Replace the starter model in src/mica.ts (news, settings) with the website's model.\",\n \"Write the model to content/.mica/schema.json: npx astro sync\",\n \"Check the content: npx mica check\",\n \"When the content is in place, open the editor in a browser: npx mica start\",\n );\n return result;\n}\n\n/** `npm run mica` opens the editor (`mica start`); `npm run check` checks the content, and the types when `@astrojs/check` is there. */\nfunction scriptsToSet(scripts: Record<string, string>, astroCheck: boolean): string[] {\n const missing: string[] = [];\n if (!scripts[\"mica\"]) missing.push(`\"mica\": \"mica start\"`);\n // Without @astrojs/check, `astro check` asks to install it, and a script cannot answer.\n const check = scripts[\"check\"];\n if (!check) missing.push(`\"check\": \"${astroCheck ? \"mica check && astro check\" : \"mica check\"}\"`);\n else if (!check.includes(\"mica check\")) missing.push(`\"check\": ${JSON.stringify(`mica check && ${check}`)}`);\n return missing;\n}\n\nfunction existingFile(project: string, candidates: string[]): string | undefined {\n return candidates.find((candidate) => existsSync(path.join(project, candidate)));\n}\n\nfunction write(project: string, file: string, contents: string, result: InitResult): void {\n mkdirSync(path.dirname(path.join(project, file)), { recursive: true });\n writeFileSync(path.join(project, file), contents, { flag: \"wx\" });\n result.created.push(file);\n}\n\n/** The result as a person or an agent reads it. */\nexport function formatInit(result: InitResult): string {\n const lines: string[] = [];\n for (const file of result.created) lines.push(` created ${file}`);\n for (const file of result.kept) lines.push(` kept ${file}`);\n lines.push(\"\", \"Next:\", \"\");\n result.next.forEach((step, index) => lines.push(` ${index + 1}. ${step}`, ...(step.includes(\"\\n\") ? [\"\"] : [])));\n return `${lines.join(\"\\n\")}\\n`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@micacms/editor",
|
|
3
|
-
"version": "0.6.0
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The local Mica editor — `npx @micacms/editor` against a checkout on your own machine. No authentication, no database, loopback only.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"hono": "^4.13.7",
|
|
32
32
|
"zod": "^4.3.6",
|
|
33
|
-
"@micacms/core": "0.6.0
|
|
34
|
-
"@micacms/editor-ui": "0.6.0
|
|
35
|
-
"@micacms/runtime": "0.6.0
|
|
33
|
+
"@micacms/core": "0.6.0",
|
|
34
|
+
"@micacms/editor-ui": "0.6.0",
|
|
35
|
+
"@micacms/runtime": "0.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.20.1",
|