@micacms/editor 0.2.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 ADDED
@@ -0,0 +1,89 @@
1
+ # Mica Editor
2
+
3
+ > **Experimental.** Pre-1.0: the CLI, the API and the content format can still
4
+ > change without notice. Use it at your own risk.
5
+
6
+ The local Mica editor: `@micacms/runtime` and `@micacms/editor-ui` composed for
7
+ a developer's own machine. No authentication, no user, no database — the
8
+ content directory is the only thing this process writes, and loopback plus a
9
+ Host allowlist are what make that safe.
10
+
11
+ ```
12
+ localhost:6422
13
+ │
14
+ ▼
15
+ Hono ── /api/* /admin/* /health
16
+ │ │
17
+ connector Vue SPA (from @micacms/editor-ui)
18
+ │
19
+ your working tree ←— the draft workspace
20
+ ```
21
+
22
+ ## Running it against a project
23
+
24
+ ```bash
25
+ npx @micacms/editor --content ./content
26
+ ```
27
+
28
+ The published package carries the API and the built admin, so this is one
29
+ command and no clone. It binds to **loopback** and refuses anything else,
30
+ answers only to `localhost` / `127.0.0.1` / `[::1]` — any other Host gets a 403
31
+ pointing at the self-hosted distribution — and prints a banner instead of
32
+ startup log lines before opening a browser on the editor.
33
+
34
+ There is no sign-in, because there is nothing to sign in to: no identity
35
+ provider, no account, no session, and no `@micacms/auth-oidc` anywhere in the
36
+ dependency graph — a boundary test walks the graph to keep it that way. There
37
+ is no database either: a save is a file in your working tree, which your
38
+ site's dev server hot-reloads, and Publish turns the reviewed changes into one
39
+ commit pushed with your own Git configuration (`MICA_LOCAL_PUBLISH=false`
40
+ keeps Publish out of the editor entirely).
41
+
42
+ ## Working on Mica itself
43
+
44
+ ```bash
45
+ rush update
46
+ rush build --to tag:packages
47
+
48
+ cd apps/local-editor
49
+ cp .env.example .env
50
+
51
+ rush dev # API on :6422, admin on :5173
52
+ ```
53
+
54
+ `CONTENT_ROOT=../../examples/muster-consulting/content` is the default, so the
55
+ server edits the example project's content. There is nothing to seed: the
56
+ content is already there, in Git.
57
+
58
+ The server has no content model of its own. It reads one from the content
59
+ directory at `.mica/schema.json`, which the Astro project publishes and the
60
+ developer commits. If it is not there, the server says so and exits.
61
+
62
+ Then open <http://localhost:5173/admin/>. The editor opens directly — no name,
63
+ no avatar and no account menu, because there is nothing behind them.
64
+
65
+ `rush dev` runs this app and the Vite dev server together. Vite
66
+ proxies `/api` to the API; the API still needs
67
+ `ADMIN_ORIGINS=http://localhost:5173` for its Origin check, which
68
+ `.env.example` already sets.
69
+
70
+ ## What lives where
71
+
72
+ ```
73
+ src/
74
+ ├── cli.ts the `mica` bin: flags, checks, environment, hand-off
75
+ ├── index.ts the composition: local connector + runtime + editor UI
76
+ ├── config.ts the local slice of the environment, and the refusals
77
+ ├── guards.ts the Host allowlist
78
+ └── banner.ts what a person sees instead of log lines
79
+ ```
80
+
81
+ Everything of substance is elsewhere: the API and services in
82
+ `@micacms/runtime`, the SPA in `@micacms/editor-ui`, the content rules in
83
+ `@micacms/core` and `@micacms/connectors`. This package is the composition the
84
+ architecture calls `apps/local-editor`, published under the name the command
85
+ deserves.
86
+
87
+ A deployment other people can reach is the other composition:
88
+ `@micacms/self-hosted`, with an OIDC provider in front — see
89
+ [docs/self-hosting.md](../../docs/self-hosting.md).
@@ -0,0 +1,3 @@
1
+ /** Where the built editor UI lives: inside the @micacms/editor-ui package. */
2
+ export declare function editorUiDist(): string | undefined;
3
+ //# sourceMappingURL=admin-dir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-dir.d.ts","sourceRoot":"","sources":["../src/admin-dir.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAO9E,wBAAgB,YAAY,IAAI,MAAM,GAAG,SAAS,CAQjD"}
@@ -0,0 +1,15 @@
1
+ /** Where the built editor UI lives: inside the @micacms/editor-ui package. */
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ const require = createRequire(import.meta.url);
5
+ export function editorUiDist() {
6
+ try {
7
+ return path.join(path.dirname(require.resolve("@micacms/editor-ui/package.json")), "dist");
8
+ }
9
+ catch {
10
+ // Running from source before an install has linked the package. The app
11
+ // falls back to its 503 explainer, which names the fix.
12
+ return undefined;
13
+ }
14
+ }
15
+ //# sourceMappingURL=admin-dir.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-dir.js","sourceRoot":"","sources":["../src/admin-dir.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,wDAAwD;QACxD,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * What `npx @micacms/editor` says when it works.
3
+ *
4
+ * The startup log lines are written for whoever is reading a container's
5
+ * output later: every field that could matter, flat, greppable, and in no
6
+ * particular order of importance. A person who has just typed a command needs
7
+ * the opposite — the address to open, the directory being edited, and what
8
+ * pressing Save will do to their repository. Three facts, in that order,
9
+ * because the first minute decides whether there is a second one.
10
+ *
11
+ * Everything here is decoration over information the server already has. It is
12
+ * printed instead of the startup log lines, never as well: two accounts of the
13
+ * same event, disagreeing in tone, is worse than either alone.
14
+ */
15
+ export interface Banner {
16
+ version: string;
17
+ /** The origin the person should open, without the `/admin/` path. */
18
+ url: string;
19
+ contentRoot: string;
20
+ collections: number;
21
+ singletons: number;
22
+ }
23
+ /** ANSI, or nothing at all when the output is not a terminal. */
24
+ export declare function colors(enabled: boolean): {
25
+ bold: (text: string) => string;
26
+ dim: (text: string) => string;
27
+ brand: (text: string) => string;
28
+ label: (text: string) => string;
29
+ };
30
+ /**
31
+ * True unless something says otherwise.
32
+ *
33
+ * `NO_COLOR` is the convention (no-color.org); a pipe or a file is the other
34
+ * case, and escape codes in a redirected log help nobody.
35
+ */
36
+ export declare function supportsColor(stream: {
37
+ isTTY?: boolean;
38
+ }, env: NodeJS.ProcessEnv): boolean;
39
+ export declare function startupBanner(banner: Banner, useColor: boolean): string;
40
+ /**
41
+ * The one failure this command is most likely to hit, and the one the operating
42
+ * system explains worst: `EADDRINUSE` alone tells nobody which port or what to
43
+ * do instead.
44
+ */
45
+ export declare function portInUse(port: number, host: string, useColor: boolean): string;
46
+ //# sourceMappingURL=banner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.d.ts","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iEAAiE;AACjE,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO;iBACC,MAAM;gBAAN,MAAM;kBAAN,MAAM;kBAAN,MAAM;EAO7C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAI1F;AAkBD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAuBvE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAQ/E"}
package/dist/banner.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * What `npx @micacms/editor` says when it works.
3
+ *
4
+ * The startup log lines are written for whoever is reading a container's
5
+ * output later: every field that could matter, flat, greppable, and in no
6
+ * particular order of importance. A person who has just typed a command needs
7
+ * the opposite — the address to open, the directory being edited, and what
8
+ * pressing Save will do to their repository. Three facts, in that order,
9
+ * because the first minute decides whether there is a second one.
10
+ *
11
+ * Everything here is decoration over information the server already has. It is
12
+ * printed instead of the startup log lines, never as well: two accounts of the
13
+ * same event, disagreeing in tone, is worse than either alone.
14
+ */
15
+ import path from "node:path";
16
+ /** ANSI, or nothing at all when the output is not a terminal. */
17
+ export function colors(enabled) {
18
+ const wrap = (code) => (text) => (enabled ? `[${code}m${text}` : text);
19
+ return {
20
+ bold: wrap("1"),
21
+ dim: wrap("2"),
22
+ brand: wrap("36"),
23
+ label: wrap("2"),
24
+ };
25
+ }
26
+ /**
27
+ * True unless something says otherwise.
28
+ *
29
+ * `NO_COLOR` is the convention (no-color.org); a pipe or a file is the other
30
+ * case, and escape codes in a redirected log help nobody.
31
+ */
32
+ export function supportsColor(stream, env) {
33
+ if (env["NO_COLOR"])
34
+ return false;
35
+ if (env["FORCE_COLOR"])
36
+ return true;
37
+ return Boolean(stream.isTTY);
38
+ }
39
+ /** Paths inside the working directory read better as the relative ones. */
40
+ function readable(target, cwd = process.cwd()) {
41
+ const relative = path.relative(cwd, target);
42
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative))
43
+ return target;
44
+ return `./${relative}`;
45
+ }
46
+ /** "3 collections and 2 singletons", or whichever half is not zero. */
47
+ function modelSummary(collections, singletons) {
48
+ const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
49
+ const parts = [];
50
+ if (collections > 0)
51
+ parts.push(plural(collections, "collection"));
52
+ if (singletons > 0)
53
+ parts.push(plural(singletons, "singleton"));
54
+ return parts.join(" and ") || "an empty model";
55
+ }
56
+ export function startupBanner(banner, useColor) {
57
+ const c = colors(useColor);
58
+ const editor = `${banner.url}/admin/`;
59
+ const row = (label, value) => ` ${c.label(label.padEnd(9))} ${value}`;
60
+ const lines = [
61
+ "",
62
+ ` ${c.brand("◆")} ${c.bold("Mica")} ${c.dim(banner.version)}`,
63
+ "",
64
+ row("Editor", c.bold(editor)),
65
+ row("Content", `${readable(banner.contentRoot)} ${c.dim(`— ${modelSummary(banner.collections, banner.singletons)}`)}`),
66
+ row("Saving", c.dim("writes files into that directory; Publish commits and pushes them.")),
67
+ "",
68
+ ` ${c.dim("No sign-in on your own machine — the link above opens straight into the editor.")}`,
69
+ ` ${c.dim("Mica keeps no state of its own here: everything you save is a file in that directory.")}`,
70
+ "",
71
+ ` ${c.dim("Run your site's dev server beside this to see the changes. Ctrl+C stops Mica.")}`,
72
+ "",
73
+ ];
74
+ return `${lines.join("\n")}\n`;
75
+ }
76
+ /**
77
+ * The one failure this command is most likely to hit, and the one the operating
78
+ * system explains worst: `EADDRINUSE` alone tells nobody which port or what to
79
+ * do instead.
80
+ */
81
+ export function portInUse(port, host, useColor) {
82
+ const c = colors(useColor);
83
+ return (`\n ${c.bold(`Port ${port} is already in use on ${host}.`)}\n\n` +
84
+ ` Something else is listening there — another Mica, most likely.\n` +
85
+ ` Stop it, or give this one a different port:\n\n` +
86
+ ` npx @micacms/editor --port ${port + 1}\n\n`);
87
+ }
88
+ //# sourceMappingURL=banner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.js","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `npx @micacms/editor` — Mica against a project on this machine.
4
+ *
5
+ * The point of this file is that adopting Mica should not mean adopting this
6
+ * repository. Everything it does is set environment variables and hand over to
7
+ * the same entry point a container runs, so there is one server with one
8
+ * configuration path and this is a front door rather than a second one.
9
+ *
10
+ * Two defaults differ from a container's, and both are deliberate:
11
+ *
12
+ * - it binds to loopback. A container binds 0.0.0.0 because it has to; a
13
+ * laptop does not, and this editor has no authentication. Anything
14
+ * reachable by anyone else runs @micacms/self-hosted with an identity
15
+ * provider — see docs/self-hosting.md.
16
+ * - it talks to a person instead of to a log collector: one short banner
17
+ * saying where the editor is and what happens when you save, and the
18
+ * browser opened on it. `MICA_CLI` is how the server is told which of the
19
+ * two audiences it has.
20
+ *
21
+ * There is no state to keep anywhere: the local editor has no database, no
22
+ * account and no session, so nothing is written outside the content
23
+ * directory.
24
+ *
25
+ * Explicit environment wins over every flag default here, and a flag wins over
26
+ * a `.env` file, because `loadConfig` leaves already-set variables alone.
27
+ */
28
+ export interface Options {
29
+ content: string;
30
+ port?: string;
31
+ host?: string;
32
+ /** Undefined means "decide from the terminal" — see `shouldOpenBrowser`. */
33
+ open?: boolean;
34
+ }
35
+ export declare function parse(argv: string[]): Options;
36
+ /**
37
+ * Opening a browser is a courtesy, so it is offered only where it can be
38
+ * welcome: an interactive terminal that somebody is sitting at. A script, a
39
+ * container and a CI job all get a server and no surprises.
40
+ */
41
+ export declare function shouldOpenBrowser(options: Options, env: NodeJS.ProcessEnv): boolean;
42
+ export declare function main(): Promise<void>;
43
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAsBD,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;AAED,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAoD1C"}
package/dist/cli.js ADDED
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `npx @micacms/editor` — Mica against a project on this machine.
4
+ *
5
+ * The point of this file is that adopting Mica should not mean adopting this
6
+ * repository. Everything it does is set environment variables and hand over to
7
+ * the same entry point a container runs, so there is one server with one
8
+ * configuration path and this is a front door rather than a second one.
9
+ *
10
+ * Two defaults differ from a container's, and both are deliberate:
11
+ *
12
+ * - it binds to loopback. A container binds 0.0.0.0 because it has to; a
13
+ * laptop does not, and this editor has no authentication. Anything
14
+ * reachable by anyone else runs @micacms/self-hosted with an identity
15
+ * provider — see docs/self-hosting.md.
16
+ * - it talks to a person instead of to a log collector: one short banner
17
+ * saying where the editor is and what happens when you save, and the
18
+ * browser opened on it. `MICA_CLI` is how the server is told which of the
19
+ * two audiences it has.
20
+ *
21
+ * There is no state to keep anywhere: the local editor has no database, no
22
+ * account and no session, so nothing is written outside the content
23
+ * directory.
24
+ *
25
+ * Explicit environment wins over every flag default here, and a flag wins over
26
+ * a `.env` file, because `loadConfig` leaves already-set variables alone.
27
+ */
28
+ import { existsSync, realpathSync } from "node:fs";
29
+ import path from "node:path";
30
+ import { pathToFileURL } from "node:url";
31
+ import { MANIFEST_PATH } from "@micacms/core";
32
+ import { DEFAULT_PORT, VERSION } from "@micacms/runtime";
33
+ const USAGE = `
34
+ mica — edit your Astro project's content, on this machine
35
+
36
+ Usage
37
+ $ npx @micacms/editor [content directory]
38
+
39
+ Options
40
+ -c, --content <dir> The content root (default: ./content)
41
+ -p, --port <number> Port to listen on (default: ${DEFAULT_PORT})
42
+ --host <address> Address to bind (default: 127.0.0.1)
43
+ --no-open Do not open the browser
44
+ -h, --help Show this
45
+ -v, --version Print the version
46
+
47
+ There is no sign-in here, because there is nothing to sign in to: this mode
48
+ has no identity provider, no account and no user. That is right for your own
49
+ machine and wrong everywhere else — a Mica other people reach runs as a
50
+ container with an identity provider. See docs/self-hosting.md.
51
+ `;
52
+ export function parse(argv) {
53
+ const options = { content: "./content" };
54
+ for (let index = 0; index < argv.length; index++) {
55
+ const flag = argv[index];
56
+ const value = () => {
57
+ const next = argv[++index];
58
+ if (next === undefined || next.startsWith("-"))
59
+ fail(`${flag} needs a value.`);
60
+ return next;
61
+ };
62
+ switch (flag) {
63
+ case "-c":
64
+ case "--content":
65
+ options.content = value();
66
+ break;
67
+ case "-p":
68
+ case "--port":
69
+ options.port = value();
70
+ break;
71
+ case "--host":
72
+ options.host = value();
73
+ break;
74
+ case "--open":
75
+ options.open = true;
76
+ break;
77
+ case "--no-open":
78
+ options.open = false;
79
+ break;
80
+ case "-h":
81
+ case "--help":
82
+ process.stdout.write(`${USAGE}\n`);
83
+ process.exit(0);
84
+ case "-v":
85
+ case "--version":
86
+ process.stdout.write(`${VERSION}\n`);
87
+ process.exit(0);
88
+ default:
89
+ // A bare path is the content root, so `npx @micacms/editor ./site/content`
90
+ // does what it looks like it does.
91
+ if (!flag.startsWith("-"))
92
+ options.content = flag;
93
+ else
94
+ fail(`Unknown option "${flag}".`);
95
+ }
96
+ }
97
+ return options;
98
+ }
99
+ /**
100
+ * A problem the person can fix, said the way a person reads.
101
+ *
102
+ * The title is the fact, the lines under it are what to do about it. Anything
103
+ * printed here has already decided the command is over: the caller exits.
104
+ */
105
+ function report(title, lines) {
106
+ const body = lines.map((line) => (line ? ` ${line}` : "")).join("\n");
107
+ process.stderr.write(`\n ${title}\n\n${body}\n\n`);
108
+ }
109
+ function fail(message) {
110
+ report(message, ["Run `npx @micacms/editor --help` to see every option."]);
111
+ process.exit(64); // EX_USAGE
112
+ }
113
+ /**
114
+ * Opening a browser is a courtesy, so it is offered only where it can be
115
+ * welcome: an interactive terminal that somebody is sitting at. A script, a
116
+ * container and a CI job all get a server and no surprises.
117
+ */
118
+ export function shouldOpenBrowser(options, env) {
119
+ if (options.open !== undefined)
120
+ return options.open;
121
+ return Boolean(process.stdout.isTTY) && !env["CI"];
122
+ }
123
+ export async function main() {
124
+ const options = parse(process.argv.slice(2));
125
+ const content = path.resolve(options.content);
126
+ if (!existsSync(content)) {
127
+ report(`There is no content directory at ${content}.`, [
128
+ "Mica edits the directory your Astro project publishes content into —",
129
+ "the `root` in your `defineSite({ root: \"./content\" })`.",
130
+ "",
131
+ " npx @micacms/editor ./path/to/content",
132
+ "",
133
+ "If this is a new project, create the directory and Mica writes the first",
134
+ "entry into it.",
135
+ ]);
136
+ process.exit(64); // EX_USAGE
137
+ }
138
+ // A far more useful message than the server's, because here we know the
139
+ // project is on this machine and can name the command that fixes it.
140
+ if (!existsSync(path.join(content, MANIFEST_PATH))) {
141
+ report(`There is no content model at ${path.join(content, MANIFEST_PATH)}.`, [
142
+ "Mica ships without one: your Astro project defines the model, and building",
143
+ "the project writes it there. Run this in the project, then start Mica again:",
144
+ "",
145
+ " npx astro build # or start the dev server once",
146
+ "",
147
+ "If you have not defined a model yet, docs/getting-started.md is five minutes.",
148
+ ]);
149
+ process.exit(78); // EX_CONFIG
150
+ }
151
+ const defaults = {
152
+ CONTENT_ROOT: content,
153
+ HOST: options.host ?? "127.0.0.1",
154
+ PORT: options.port ?? String(DEFAULT_PORT),
155
+ // Tells the server it is talking to a person: one banner instead of the
156
+ // startup log lines a collector wants.
157
+ MICA_CLI: "true",
158
+ MICA_OPEN: String(shouldOpenBrowser(options, process.env)),
159
+ };
160
+ for (const [key, value] of Object.entries(defaults)) {
161
+ process.env[key] ??= value;
162
+ }
163
+ // MICA_PUBLIC_URL has to agree with the port, and only matters here for
164
+ // cookie scope and the Origin check — both of which are this process's own
165
+ // address.
166
+ process.env["MICA_PUBLIC_URL"] ??= `http://localhost:${process.env["PORT"]}`;
167
+ // The server is imported rather than spawned: one process, one signal
168
+ // handler, and a stack trace that points at the real thing.
169
+ await import("./index.js");
170
+ }
171
+ // Only when this file *is* the command. Importing it — which the tests do, and
172
+ // which a future `mica init` would — must not start a server.
173
+ //
174
+ // `realpathSync` matters: npm and npx run this file through a symlink in
175
+ // `node_modules/.bin`, and Node resolves `import.meta.url` to the symlink's
176
+ // real target. Comparing against the raw `argv[1]` never matched there, so
177
+ // `npx @micacms/editor` silently did nothing.
178
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
179
+ main().catch((error) => {
180
+ process.stderr.write(`\n${error instanceof Error ? error.stack : String(error)}\n`);
181
+ process.exit(1);
182
+ });
183
+ }
184
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The local editor's configuration.
3
+ *
4
+ * Small on purpose: the variables the runtime reads, minus everything that
5
+ * describes a deployment. There is no `REPOSITORY` — this app *is* the local
6
+ * connector against a checkout — no `DATA_DIR`, because it keeps no state,
7
+ * and no OIDC or admission variable exists to be set, which is a stronger
8
+ * statement than ignoring one.
9
+ *
10
+ * The refusals are the containment for a server with no authentication:
11
+ * production is refused, a non-local public URL is refused, and any listening
12
+ * address but loopback is refused, with no override. Remote access is what
13
+ * the self-hosted distribution is.
14
+ */
15
+ import { type RuntimeConfig } from "@micacms/runtime";
16
+ export type LocalConfig = RuntimeConfig & {
17
+ MICA_CLI: boolean;
18
+ MICA_OPEN: boolean;
19
+ };
20
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): LocalConfig;
21
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EASL,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAsD1B,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpF,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,WAAW,CAkD5E"}
package/dist/config.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The local editor's configuration.
3
+ *
4
+ * Small on purpose: the variables the runtime reads, minus everything that
5
+ * describes a deployment. There is no `REPOSITORY` — this app *is* the local
6
+ * connector against a checkout — no `DATA_DIR`, because it keeps no state,
7
+ * and no OIDC or admission variable exists to be set, which is a stronger
8
+ * statement than ignoring one.
9
+ *
10
+ * The refusals are the containment for a server with no authentication:
11
+ * production is refused, a non-local public URL is refused, and any listening
12
+ * address but loopback is refused, with no override. Remote access is what
13
+ * the self-hosted distribution is.
14
+ */
15
+ import { z } from "zod";
16
+ import { bool, ConfigError, deriveRuntime, describeConfigIssues, loadEnvFile, LOOPBACK, runtimeEnv, withoutBlanks, } from "@micacms/runtime";
17
+ const { NODE_ENV, LOG_LEVEL, HOST, PORT, MICA_PUBLIC_URL, CONTENT_ROOT, MICA_LOCAL_PUBLISH, MAX_UPLOAD_BYTES, ADMIN_ORIGINS, ADMIN_DIR, } = runtimeEnv;
18
+ const schema = z
19
+ .object({
20
+ NODE_ENV,
21
+ LOG_LEVEL,
22
+ HOST,
23
+ PORT,
24
+ MICA_PUBLIC_URL,
25
+ CONTENT_ROOT,
26
+ MICA_LOCAL_PUBLISH,
27
+ MAX_UPLOAD_BYTES,
28
+ ADMIN_ORIGINS,
29
+ ADMIN_DIR,
30
+ /**
31
+ * Set by the `mica` bin, and by nothing else. The audience for the
32
+ * startup output: a person at a terminal gets one banner, a log collector
33
+ * gets the structured lines.
34
+ */
35
+ MICA_CLI: bool(false),
36
+ /** Whether that front door should open a browser once it is listening. */
37
+ MICA_OPEN: bool(false),
38
+ })
39
+ .transform((env) => ({
40
+ ...deriveRuntime({
41
+ ...env,
42
+ // Fixed, not configurable: the local editor edits a checkout. A remote
43
+ // repository is the self-hosted app's job.
44
+ REPOSITORY: "local",
45
+ REPOSITORY_ROOT: "content",
46
+ REPOSITORY_BRANCH: "main",
47
+ REPOSITORY_TOKEN: undefined,
48
+ REPOSITORY_PROJECT: undefined,
49
+ REPOSITORY_API_BASE: undefined,
50
+ // There is no proxy in front of a loopback server to trust.
51
+ TRUST_PROXY: false,
52
+ }),
53
+ HOST: env.HOST ?? "127.0.0.1",
54
+ }));
55
+ export function loadConfig(env = process.env) {
56
+ // Only when reading the ambient environment: tests pass an explicit object
57
+ // and must not pick up whatever `.env` sits in the working directory.
58
+ if (env === process.env)
59
+ loadEnvFile();
60
+ const parsed = schema.safeParse(withoutBlanks(env));
61
+ if (!parsed.success)
62
+ throw new ConfigError(describeConfigIssues(parsed.error));
63
+ const config = parsed.data;
64
+ /**
65
+ * The local editor has no authentication. It must never be reachable.
66
+ *
67
+ * This is the single most dangerous configuration Mica can be in, so it is
68
+ * refused at the three points where it stops being a developer's
69
+ * convenience: a production build, a base URL that is not the local
70
+ * machine, and a listening address that is not the local machine either.
71
+ */
72
+ if (config.isProduction) {
73
+ throw new ConfigError("The local editor has no authentication, and NODE_ENV=production.\n" +
74
+ " There is no identity provider and no user: every request is allowed.\n" +
75
+ " A deployment runs @micacms/self-hosted with an OIDC provider instead —\n" +
76
+ " see docs/self-hosting.md.");
77
+ }
78
+ const host = new URL(config.MICA_PUBLIC_URL).hostname;
79
+ if (!LOOPBACK.has(host)) {
80
+ throw new ConfigError(`The local editor has no authentication, but MICA_PUBLIC_URL points at "${host}".\n` +
81
+ " It is for a developer's own machine only. Anything reachable by anyone\n" +
82
+ " else runs @micacms/self-hosted with an identity provider.");
83
+ }
84
+ /**
85
+ * The same rule for the socket. A base URL saying `localhost` while the
86
+ * process listens on `0.0.0.0` leaves an unauthenticated editor open to the
87
+ * whole network. There is deliberately no override.
88
+ */
89
+ if (!LOOPBACK.has(config.HOST) && config.HOST !== "::1") {
90
+ throw new ConfigError(`The local editor has no authentication, but HOST is "${config.HOST}".\n` +
91
+ " It binds to the loopback address and nothing else. Use HOST=127.0.0.1,\n" +
92
+ " or run @micacms/self-hosted with an identity provider.");
93
+ }
94
+ return config;
95
+ }
96
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,IAAI,EACJ,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,UAAU,EACV,aAAa,GAEd,MAAM,kBAAkB,CAAC;AAE1B,MAAM,EACJ,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,IAAI,EACJ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,SAAS,GACV,GAAG,UAAU,CAAC;AAEf,MAAM,MAAM,GAAG,CAAC;KACb,MAAM,CAAC;IACN,QAAQ;IACR,SAAS;IACT,IAAI;IACJ,IAAI;IACJ,eAAe;IACf,YAAY;IACZ,kBAAkB;IAClB,gBAAgB;IAChB,aAAa;IACb,SAAS;IAET;;;;OAIG;IACH,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC;IACrB,0EAA0E;IAC1E,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;CACvB,CAAC;KACD,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACnB,GAAG,aAAa,CAAC;QACf,GAAG,GAAG;QACN,uEAAuE;QACvE,2CAA2C;QAC3C,UAAU,EAAE,OAAgB;QAC5B,eAAe,EAAE,SAAS;QAC1B,iBAAiB,EAAE,MAAM;QACzB,gBAAgB,EAAE,SAAS;QAC3B,kBAAkB,EAAE,SAAS;QAC7B,mBAAmB,EAAE,SAAS;QAC9B,4DAA4D;QAC5D,WAAW,EAAE,KAAK;KACnB,CAAC;IACF,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,WAAW;CAC9B,CAAC,CAAC,CAAC;AAIN,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,2EAA2E;IAC3E,sEAAsE;IACtE,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG;QAAE,WAAW,EAAE,CAAC;IAEvC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,MAAM,IAAI,WAAW,CAAC,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/E,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC;IAE3B;;;;;;;OAOG;IACH,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,MAAM,IAAI,WAAW,CACnB,oEAAoE;YAClE,0EAA0E;YAC1E,4EAA4E;YAC5E,6BAA6B,CAChC,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC;IACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,WAAW,CACnB,0EAA0E,IAAI,MAAM;YAClF,4EAA4E;YAC5E,6DAA6D,CAChE,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACxD,MAAM,IAAI,WAAW,CACnB,wDAAwD,MAAM,CAAC,IAAI,MAAM;YACvE,4EAA4E;YAC5E,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The Host allowlist — what replaced the local session's cookie.
3
+ *
4
+ * Under DNS rebinding, a page re-resolves its own name to `127.0.0.1` and the
5
+ * browser then treats requests to it as same-origin. Writes still fail on the
6
+ * Origin check; *reads* would succeed, and the only thing that used to stop
7
+ * them was a session cookie scoped to `localhost` that the rebound name never
8
+ * received. The cookie is gone — local mode keeps no session, no token and no
9
+ * database — so the rule is stated directly instead: this server answers only
10
+ * to names that are this machine.
11
+ *
12
+ * Smaller than what it replaces, standard for exactly this, and applied in
13
+ * front of everything, /health included.
14
+ */
15
+ import type { MiddlewareHandler } from "hono";
16
+ import type { Env } from "@micacms/runtime";
17
+ export declare function hostGuard(): MiddlewareHandler<Env>;
18
+ //# sourceMappingURL=guards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAa5C,wBAAgB,SAAS,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAgBlD"}
package/dist/guards.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The Host allowlist — what replaced the local session's cookie.
3
+ *
4
+ * Under DNS rebinding, a page re-resolves its own name to `127.0.0.1` and the
5
+ * browser then treats requests to it as same-origin. Writes still fail on the
6
+ * Origin check; *reads* would succeed, and the only thing that used to stop
7
+ * them was a session cookie scoped to `localhost` that the rebound name never
8
+ * received. The cookie is gone — local mode keeps no session, no token and no
9
+ * database — so the rule is stated directly instead: this server answers only
10
+ * to names that are this machine.
11
+ *
12
+ * Smaller than what it replaces, standard for exactly this, and applied in
13
+ * front of everything, /health included.
14
+ */
15
+ const ALLOWED = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
16
+ /** The hostname part of a Host header, brackets kept for IPv6. */
17
+ function hostnameOf(header) {
18
+ if (header.startsWith("[")) {
19
+ const end = header.indexOf("]");
20
+ return end < 0 ? header : header.slice(0, end + 1);
21
+ }
22
+ return header.split(":")[0] ?? "";
23
+ }
24
+ export function hostGuard() {
25
+ return async (c, next) => {
26
+ const host = c.req.header("host") ?? "";
27
+ if (!ALLOWED.has(hostnameOf(host).toLowerCase())) {
28
+ return c.text("This is a local Mica editor. It answers only to this machine —\n" +
29
+ `"${host}" is not it.\n\n` +
30
+ "To let other people edit, deploy @micacms/self-hosted with an identity\n" +
31
+ "provider: https://github.com/amuedespacher/micacms/blob/main/docs/self-hosting.md\n", 403);
32
+ }
33
+ await next();
34
+ };
35
+ }
36
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAEpE,kEAAkE;AAClE,SAAS,UAAU,CAAC,MAAc;IAChC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAExC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACjD,OAAO,CAAC,CAAC,IAAI,CACX,kEAAkE;gBAChE,IAAI,IAAI,kBAAkB;gBAC1B,0EAA0E;gBAC1E,qFAAqF,EACvF,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The local editor's process entry point.
3
+ *
4
+ * The whole composition, in one page: the local connector, the
5
+ * auth-neutral runtime with `allowAll()`, the Host allowlist, and nothing
6
+ * else. No session service, no admission policy, no database — `npx
7
+ * @micacms/editor` writes nothing outside the content directory, and there is
8
+ * no auth code here to misconfigure because none is installed.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The local editor's process entry point.
3
+ *
4
+ * The whole composition, in one page: the local connector, the
5
+ * auth-neutral runtime with `allowAll()`, the Host allowlist, and nothing
6
+ * else. No session service, no admission policy, no database — `npx
7
+ * @micacms/editor` writes nothing outside the content directory, and there is
8
+ * no auth code here to misconfigure because none is installed.
9
+ */
10
+ import { localConnector } from "@micacms/connectors";
11
+ import { MANIFEST_PATH } from "@micacms/core";
12
+ import { ConfigError, createApp, createLogger, createRuntimeContext, loadSchema, SchemaError, schemaFingerprint, schemaOrigin, startServer, VERSION, } from "@micacms/runtime";
13
+ import { editorUiDist } from "./admin-dir.js";
14
+ import { portInUse, startupBanner, supportsColor } from "./banner.js";
15
+ import { loadConfig } from "./config.js";
16
+ import { hostGuard } from "./guards.js";
17
+ import { openInBrowser } from "./open.js";
18
+ async function main() {
19
+ let config;
20
+ try {
21
+ config = loadConfig();
22
+ }
23
+ catch (error) {
24
+ if (error instanceof ConfigError) {
25
+ process.stderr.write(`\n${error.message}\n\n`);
26
+ process.exit(78); // EX_CONFIG
27
+ }
28
+ throw error;
29
+ }
30
+ const logger = createLogger({ level: config.LOG_LEVEL, pretty: true });
31
+ // The repository comes first, because the content model is in it. A bad or
32
+ // absent model is a project mistake rather than a crash: exit the way a bad
33
+ // environment does, with the message on stderr instead of a stack trace.
34
+ const connector = localConnector({ root: config.contentRoot });
35
+ let model;
36
+ try {
37
+ model = await loadSchema(connector);
38
+ }
39
+ catch (error) {
40
+ if (error instanceof SchemaError) {
41
+ process.stderr.write(`\n${error.message}\n\n`);
42
+ process.exit(78); // EX_CONFIG
43
+ }
44
+ throw error;
45
+ }
46
+ const schemaRevision = await connector.revision(MANIFEST_PATH);
47
+ const ctx = createRuntimeContext({
48
+ config,
49
+ model,
50
+ connector,
51
+ schemaRevision,
52
+ logger,
53
+ operational: () => ({
54
+ auth: "none — the local editor has no accounts and no sign-in",
55
+ database: null,
56
+ }),
57
+ });
58
+ const app = createApp(ctx, {
59
+ guards: [hostGuard()],
60
+ adminDir: config.ADMIN_DIR ?? editorUiDist(),
61
+ });
62
+ const fingerprint = await schemaFingerprint(model);
63
+ startServer({
64
+ app,
65
+ host: config.HOST,
66
+ port: config.PORT,
67
+ logger,
68
+ onListening() {
69
+ /**
70
+ * Two audiences, one event: the person who just typed a command gets an
71
+ * address and three facts; a log collector gets every field.
72
+ */
73
+ if (config.MICA_CLI) {
74
+ const useColor = supportsColor(process.stdout, process.env);
75
+ process.stdout.write(startupBanner({
76
+ version: VERSION,
77
+ url: config.MICA_PUBLIC_URL,
78
+ contentRoot: connector.origin,
79
+ collections: Object.keys(model.collections).length,
80
+ singletons: Object.keys(model.singletons).length,
81
+ }, useColor));
82
+ if (config.MICA_OPEN)
83
+ openInBrowser(`${config.MICA_PUBLIC_URL}/admin/`);
84
+ return;
85
+ }
86
+ logger.info("Mica local editor started", {
87
+ url: config.MICA_PUBLIC_URL,
88
+ listening: `${config.HOST}:${config.PORT}`,
89
+ auth: "none (local editor; loopback only)",
90
+ content: connector.origin,
91
+ schema: schemaOrigin(connector),
92
+ schemaFingerprint: fingerprint,
93
+ collections: Object.keys(model.collections).length,
94
+ });
95
+ },
96
+ portInUseMessage: () => portInUse(config.PORT, config.HOST, supportsColor(process.stderr, process.env)),
97
+ shutdown: () => ctx.shutdown(),
98
+ });
99
+ }
100
+ main().catch((error) => {
101
+ process.stderr.write(`Failed to start: ${error instanceof Error ? error.stack : String(error)}\n`);
102
+ process.exit(1);
103
+ });
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/open.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Opens a URL in whatever the desktop considers a browser.
3
+ *
4
+ * A courtesy, and treated as one: every failure is swallowed. A machine with no
5
+ * desktop session — over SSH, in a container, in CI — has nothing to open, and
6
+ * a server that refused to start because of it would be absurd.
7
+ */
8
+ export declare function openInBrowser(url: string): void;
9
+ //# sourceMappingURL=open.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../src/open.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAmB/C"}
package/dist/open.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Opens a URL in whatever the desktop considers a browser.
3
+ *
4
+ * A courtesy, and treated as one: every failure is swallowed. A machine with no
5
+ * desktop session — over SSH, in a container, in CI — has nothing to open, and
6
+ * a server that refused to start because of it would be absurd.
7
+ */
8
+ import { spawn } from "node:child_process";
9
+ export function openInBrowser(url) {
10
+ const [command, args] = process.platform === "darwin"
11
+ ? ["open", [url]]
12
+ : process.platform === "win32"
13
+ ? // The empty string is `start`'s window title. Without it, a quoted URL
14
+ // is taken as the title and nothing opens.
15
+ ["cmd", ["/c", "start", "", url]]
16
+ : ["xdg-open", [url]];
17
+ try {
18
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
19
+ child.on("error", () => { });
20
+ // Detached and unreferenced, so a browser that outlives Mica does not keep
21
+ // the Node process alive after Ctrl+C.
22
+ child.unref();
23
+ }
24
+ catch {
25
+ // There is no browser here. That is not an error.
26
+ }
27
+ }
28
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../src/open.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GACnB,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,uEAAuE;gBACvE,2CAA2C;gBAC3C,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAE5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAQ,EAAE,IAAgB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACrF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5B,2EAA2E;QAC3E,uCAAuC;QACvC,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;IACpD,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@micacms/editor",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "The local Mica editor — `npx @micacms/editor` against a checkout on your own machine. No authentication, no database, loopback only.",
6
+ "license": "MIT",
7
+ "author": "Alexander Müdespacher",
8
+ "keywords": [
9
+ "mica",
10
+ "cms",
11
+ "astro",
12
+ "git",
13
+ "markdoc",
14
+ "headless-cms"
15
+ ],
16
+ "bin": {
17
+ "mica": "./dist/cli.js"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "dependencies": {
24
+ "hono": "^4.6.20",
25
+ "zod": "^4.3.6",
26
+ "@micacms/editor-ui": "0.2.0",
27
+ "@micacms/core": "0.2.0",
28
+ "@micacms/runtime": "0.2.0",
29
+ "@micacms/connectors": "0.2.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.20.1",
33
+ "tsx": "^4.19.2",
34
+ "typescript": "^5.7.3"
35
+ },
36
+ "engines": {
37
+ "node": ">=22.12.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.json",
44
+ "dev": "tsx watch --clear-screen=false --include ../../examples/muster-consulting/content/.mica/schema.json src/index.ts",
45
+ "start": "node dist/index.js",
46
+ "typecheck": "tsc -p tsconfig.test.json",
47
+ "test": "tsc -p tsconfig.test.json && node --test --import tsx test/*.test.ts"
48
+ }
49
+ }