@micacms/runtime 0.4.1 → 0.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/app.d.ts +8 -37
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +7 -42
- package/dist/app.js.map +1 -1
- package/dist/config.d.ts +12 -82
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -82
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +37 -66
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +24 -75
- package/dist/context.js.map +1 -1
- package/dist/http.d.ts +8 -16
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +6 -13
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +13 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -14
- package/dist/index.js.map +1 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/file-type.js.map +1 -1
- package/dist/lib/id.js.map +1 -1
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/ports.js.map +1 -1
- package/dist/lib/rate-limit.js.map +1 -1
- package/dist/lib/schema-stats.js.map +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/middleware/index.js.map +1 -1
- package/dist/routes/admin-content.d.ts +4 -17
- package/dist/routes/admin-content.d.ts.map +1 -1
- package/dist/routes/admin-content.js +11 -51
- package/dist/routes/admin-content.js.map +1 -1
- package/dist/routes/admin-media.d.ts +3 -14
- package/dist/routes/admin-media.d.ts.map +1 -1
- package/dist/routes/admin-media.js +7 -29
- package/dist/routes/admin-media.js.map +1 -1
- package/dist/routes/admin-spa.js.map +1 -1
- package/dist/routes/admin-staging.d.ts +4 -9
- package/dist/routes/admin-staging.d.ts.map +1 -1
- package/dist/routes/admin-staging.js +31 -18
- package/dist/routes/admin-staging.js.map +1 -1
- package/dist/routes/admin-system.d.ts +2 -8
- package/dist/routes/admin-system.d.ts.map +1 -1
- package/dist/routes/admin-system.js +9 -44
- package/dist/routes/admin-system.js.map +1 -1
- package/dist/routes/health.d.ts +3 -10
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +9 -26
- package/dist/routes/health.js.map +1 -1
- package/dist/schema.d.ts +10 -38
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +14 -65
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +2 -10
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -9
- package/dist/server.js.map +1 -1
- package/dist/services/changes.d.ts +150 -0
- package/dist/services/changes.d.ts.map +1 -0
- package/dist/services/changes.js +339 -0
- package/dist/services/changes.js.map +1 -0
- package/dist/services/content-store.d.ts +61 -0
- package/dist/services/content-store.d.ts.map +1 -0
- package/dist/services/content-store.js +330 -0
- package/dist/services/content-store.js.map +1 -0
- package/dist/services/media.d.ts +9 -29
- package/dist/services/media.d.ts.map +1 -1
- package/dist/services/media.js +17 -57
- package/dist/services/media.js.map +1 -1
- package/dist/services/staging.d.ts +35 -167
- package/dist/services/staging.d.ts.map +1 -1
- package/dist/services/staging.js +195 -458
- package/dist/services/staging.js.map +1 -1
- package/dist/workspace/developer.d.ts +14 -0
- package/dist/workspace/developer.d.ts.map +1 -0
- package/dist/workspace/developer.js +31 -0
- package/dist/workspace/developer.js.map +1 -0
- package/dist/workspace/files.d.ts +70 -0
- package/dist/workspace/files.d.ts.map +1 -0
- package/dist/workspace/files.js +176 -0
- package/dist/workspace/files.js.map +1 -0
- package/dist/workspace/git.d.ts +45 -0
- package/dist/workspace/git.d.ts.map +1 -0
- package/dist/workspace/git.js +68 -0
- package/dist/workspace/git.js.map +1 -0
- package/dist/workspace/locks.d.ts +33 -0
- package/dist/workspace/locks.d.ts.map +1 -0
- package/dist/workspace/locks.js +76 -0
- package/dist/workspace/locks.js.map +1 -0
- package/dist/workspace/policy.d.ts +92 -0
- package/dist/workspace/policy.d.ts.map +1 -0
- package/dist/workspace/policy.js +7 -0
- package/dist/workspace/policy.js.map +1 -0
- package/dist/workspace/tree.d.ts +73 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/tree.js +229 -0
- package/dist/workspace/tree.js.map +1 -0
- package/package.json +2 -3
- package/dist/db/driver.d.ts +0 -35
- package/dist/db/driver.d.ts.map +0 -1
- package/dist/db/driver.js +0 -95
- package/dist/db/driver.js.map +0 -1
- package/dist/lib/git.d.ts +0 -29
- package/dist/lib/git.d.ts.map +0 -1
- package/dist/lib/git.js +0 -62
- package/dist/lib/git.js.map +0 -1
- package/dist/services/staging-local.d.ts +0 -58
- package/dist/services/staging-local.d.ts.map +0 -1
- package/dist/services/staging-local.js +0 -391
- package/dist/services/staging-local.js.map +0 -1
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admin
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* onto the API's error envelope.
|
|
7
|
-
*
|
|
8
|
-
* `committed-pending-push` is a 200, deliberately. The commit is real and the
|
|
9
|
-
* state is recoverable by design; an error status would make the editor treat
|
|
10
|
-
* a publication that half-happened as one that did not happen at all.
|
|
2
|
+
* Admin routes for the pending changes and Publish. Sync and conflict
|
|
3
|
+
* resolution exist only for a clone that Mica keeps. `committed-pending-push`
|
|
4
|
+
* is a 200: the commit is real, and an error status would tell the editor that
|
|
5
|
+
* nothing happened.
|
|
11
6
|
*/
|
|
12
|
-
import { ConflictError } from "@micacms/connectors";
|
|
13
7
|
import { Hono } from "hono";
|
|
14
8
|
import { z } from "zod";
|
|
15
|
-
import { ApiError, badRequest } from "../lib/errors.js";
|
|
16
9
|
import { actorOf } from "../http.js";
|
|
10
|
+
import { ApiError, badRequest } from "../lib/errors.js";
|
|
11
|
+
import { ConflictError } from "../workspace/files.js";
|
|
17
12
|
const publishSchema = z.object({
|
|
18
13
|
message: z.string().max(500),
|
|
19
|
-
/** The
|
|
20
|
-
|
|
14
|
+
/** The reviewed paths, with the revision each had in the review. */
|
|
15
|
+
paths: z.record(z.string().min(1), z.string().nullable()),
|
|
16
|
+
});
|
|
17
|
+
const resolveSchema = z.object({
|
|
18
|
+
group: z.string().min(1),
|
|
19
|
+
choice: z.enum(["mine", "website"]),
|
|
21
20
|
});
|
|
22
21
|
const discardSchema = z.object({ paths: z.array(z.string().min(1)).min(1).max(2000) });
|
|
23
22
|
const compareSchema = z.discriminatedUnion("kind", [
|
|
@@ -52,8 +51,7 @@ export function adminStagingRoutes() {
|
|
|
52
51
|
const { staging } = c.get("ctx").services;
|
|
53
52
|
return c.json(await staging.status());
|
|
54
53
|
});
|
|
55
|
-
//
|
|
56
|
-
// raw data on both sides; the editor renders the comparison from the model.
|
|
54
|
+
// Raw data before and after one pending change; the editor renders the comparison.
|
|
57
55
|
app.get("/changes/compare", async (c) => {
|
|
58
56
|
const { staging } = c.get("ctx").services;
|
|
59
57
|
const parsed = compareSchema.safeParse({
|
|
@@ -65,7 +63,8 @@ export function adminStagingRoutes() {
|
|
|
65
63
|
});
|
|
66
64
|
if (!parsed.success)
|
|
67
65
|
throw badRequest("The compare target is malformed.");
|
|
68
|
-
|
|
66
|
+
const against = c.req.query("against") === "origin" ? "origin" : "head";
|
|
67
|
+
return c.json(await staging.compare(parsed.data, against));
|
|
69
68
|
});
|
|
70
69
|
app.post("/publish", async (c) => {
|
|
71
70
|
const { staging } = c.get("ctx").services;
|
|
@@ -75,8 +74,8 @@ export function adminStagingRoutes() {
|
|
|
75
74
|
const actor = actorOf(c);
|
|
76
75
|
const result = await handled(() => staging.publish({
|
|
77
76
|
message: parsed.data.message,
|
|
78
|
-
|
|
79
|
-
author: actor ? { name: actor.email, email: actor.email } : undefined,
|
|
77
|
+
paths: parsed.data.paths,
|
|
78
|
+
author: actor ? { name: actor.name || actor.email, email: actor.email } : undefined,
|
|
80
79
|
}));
|
|
81
80
|
return c.json(result);
|
|
82
81
|
});
|
|
@@ -84,6 +83,20 @@ export function adminStagingRoutes() {
|
|
|
84
83
|
const { staging } = c.get("ctx").services;
|
|
85
84
|
return c.json(await handled(() => staging.push()));
|
|
86
85
|
});
|
|
86
|
+
// "Try again" after a sync error.
|
|
87
|
+
app.post("/sync", async (c) => {
|
|
88
|
+
const { staging } = c.get("ctx").services;
|
|
89
|
+
await staging.sync();
|
|
90
|
+
return c.json(await staging.status());
|
|
91
|
+
});
|
|
92
|
+
app.post("/changes/resolve", async (c) => {
|
|
93
|
+
const { staging } = c.get("ctx").services;
|
|
94
|
+
const parsed = resolveSchema.safeParse(await jsonBody(c));
|
|
95
|
+
if (!parsed.success)
|
|
96
|
+
throw badRequest("Expected a group and a choice: mine or website.");
|
|
97
|
+
await handled(() => staging.resolve(parsed.data.group, parsed.data.choice));
|
|
98
|
+
return c.json(await staging.status());
|
|
99
|
+
});
|
|
87
100
|
app.post("/changes/discard", async (c) => {
|
|
88
101
|
const { staging } = c.get("ctx").services;
|
|
89
102
|
const parsed = discardSchema.safeParse(await jsonBody(c));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-staging.js","sourceRoot":"","sources":["../../src/routes/admin-staging.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"admin-staging.js","sourceRoot":"","sources":["../../src/routes/admin-staging.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin routes for the pending changes and Publish. Sync and conflict\n * resolution exist only for a clone that Mica keeps. `committed-pending-push`\n * is a 200: the commit is real, and an error status would tell the editor that\n * nothing happened.\n */\n\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\n\nimport { actorOf, type Env } from \"../http.js\";\nimport { ApiError, badRequest } from \"../lib/errors.js\";\nimport { ConflictError } from \"../workspace/files.js\";\n\nconst publishSchema = z.object({\n message: z.string().max(500),\n /** The reviewed paths, with the revision each had in the review. */\n paths: z.record(z.string().min(1), z.string().nullable()),\n});\n\nconst resolveSchema = z.object({\n group: z.string().min(1),\n choice: z.enum([\"mine\", \"website\"]),\n});\n\nconst discardSchema = z.object({ paths: z.array(z.string().min(1)).min(1).max(2000) });\n\nconst compareSchema = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"entry\"),\n collection: z.string().min(1),\n slug: z.string().min(1),\n from: z.string().min(1).optional(),\n }),\n z.object({ kind: z.literal(\"singleton\"), name: z.string().min(1) }),\n z.object({ kind: z.literal(\"order\"), collection: z.string().min(1) }),\n]);\n\nasync function jsonBody(c: { req: { json(): Promise<unknown> } }): Promise<unknown> {\n return c.req.json().catch(() => {\n throw badRequest(\"The request body must be valid JSON.\");\n });\n}\n\n/** A repository conflict is 409 and recoverable — refresh, review, retry. */\nasync function handled<T>(run: () => Promise<T>): Promise<T> {\n try {\n return await run();\n } catch (error) {\n if (error instanceof ConflictError) throw new ApiError(\"conflict\", error.message);\n throw error;\n }\n}\n\nexport function adminStagingRoutes(): Hono<Env> {\n const app = new Hono<Env>();\n\n app.get(\"/changes\", async (c) => {\n const { staging } = c.get(\"ctx\").services;\n return c.json(await staging.status());\n });\n\n // Raw data before and after one pending change; the editor renders the comparison.\n app.get(\"/changes/compare\", async (c) => {\n const { staging } = c.get(\"ctx\").services;\n const parsed = compareSchema.safeParse({\n kind: c.req.query(\"kind\"),\n collection: c.req.query(\"collection\"),\n slug: c.req.query(\"slug\"),\n from: c.req.query(\"from\"),\n name: c.req.query(\"name\"),\n });\n if (!parsed.success) throw badRequest(\"The compare target is malformed.\");\n const against = c.req.query(\"against\") === \"origin\" ? \"origin\" : \"head\";\n\n return c.json(await staging.compare(parsed.data, against));\n });\n\n app.post(\"/publish\", async (c) => {\n const { staging } = c.get(\"ctx\").services;\n const parsed = publishSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"The publish payload is malformed.\");\n\n const actor = actorOf(c);\n const result = await handled(() =>\n staging.publish({\n message: parsed.data.message,\n paths: parsed.data.paths,\n author: actor ? { name: actor.name || actor.email, email: actor.email } : undefined,\n }),\n );\n\n return c.json(result);\n });\n\n app.post(\"/publish/push\", async (c) => {\n const { staging } = c.get(\"ctx\").services;\n return c.json(await handled(() => staging.push()));\n });\n\n // \"Try again\" after a sync error.\n app.post(\"/sync\", async (c) => {\n const { staging } = c.get(\"ctx\").services;\n await staging.sync();\n return c.json(await staging.status());\n });\n\n app.post(\"/changes/resolve\", async (c) => {\n const { staging } = c.get(\"ctx\").services;\n const parsed = resolveSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"Expected a group and a choice: mine or website.\");\n\n await handled(() => staging.resolve(parsed.data.group, parsed.data.choice));\n return c.json(await staging.status());\n });\n\n app.post(\"/changes/discard\", async (c) => {\n const { staging } = c.get(\"ctx\").services;\n const parsed = discardSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"Expected a list of change paths.\");\n\n return c.json(await handled(() => staging.discard(parsed.data.paths)));\n });\n\n return app;\n}\n"]}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema, dashboard and system information.
|
|
3
|
-
*
|
|
4
|
-
* The schema endpoint is what makes the admin generic: the SPA ships no
|
|
5
|
-
* knowledge of any particular collection, it renders whatever this returns.
|
|
6
|
-
*
|
|
7
|
-
* There is no publish route. Publishing is a commit, and the commit happened
|
|
8
|
-
* when the editor pressed Save; what follows belongs to the repository's CI,
|
|
9
|
-
* which Mica neither owns nor should pretend to.
|
|
2
|
+
* Schema, dashboard and system information. The SPA knows no collection; it
|
|
3
|
+
* renders what `/schema` returns.
|
|
10
4
|
*/
|
|
11
5
|
import { Hono } from "hono";
|
|
12
6
|
import type { Env } from "../http.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-system.d.ts","sourceRoot":"","sources":["../../src/routes/admin-system.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"admin-system.d.ts","sourceRoot":"","sources":["../../src/routes/admin-system.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAMtC,wBAAgB,iBAAiB,IAAI,IAAI,CAAC,GAAG,CAAC,CAgI7C"}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema, dashboard and system information.
|
|
3
|
-
*
|
|
4
|
-
* The schema endpoint is what makes the admin generic: the SPA ships no
|
|
5
|
-
* knowledge of any particular collection, it renders whatever this returns.
|
|
6
|
-
*
|
|
7
|
-
* There is no publish route. Publishing is a commit, and the commit happened
|
|
8
|
-
* when the editor pressed Save; what follows belongs to the repository's CI,
|
|
9
|
-
* which Mica neither owns nor should pretend to.
|
|
2
|
+
* Schema, dashboard and system information. The SPA knows no collection; it
|
|
3
|
+
* renders what `/schema` returns.
|
|
10
4
|
*/
|
|
11
5
|
import { Hono } from "hono";
|
|
12
6
|
import { listedFields, resolveSlugField, resolveTitleField } from "@micacms/core";
|
|
@@ -16,11 +10,7 @@ import { VERSION } from "../lib/version.js";
|
|
|
16
10
|
import { formatBytes } from "../services/media.js";
|
|
17
11
|
export function adminSystemRoutes() {
|
|
18
12
|
const app = new Hono();
|
|
19
|
-
/**
|
|
20
|
-
* The content model, plus the few derived values the UI would otherwise have
|
|
21
|
-
* to recompute. Field descriptors are already plain data, so the model
|
|
22
|
-
* serialises as-is.
|
|
23
|
-
*/
|
|
13
|
+
/** The content model, plus the derived values that the UI would otherwise recompute. */
|
|
24
14
|
app.get("/schema", (c) => {
|
|
25
15
|
const { site } = c.get("ctx");
|
|
26
16
|
return c.json({
|
|
@@ -47,8 +37,6 @@ export function adminSystemRoutes() {
|
|
|
47
37
|
app.get("/overview", async (c) => {
|
|
48
38
|
const ctx = c.get("ctx");
|
|
49
39
|
const { content, media } = ctx.services;
|
|
50
|
-
// Read from the repository rather than counted in SQL. A count that can
|
|
51
|
-
// disagree with the files is worse than no count.
|
|
52
40
|
const collections = await Promise.all(Object.values(ctx.site.model.collections).map(async (def) => ({
|
|
53
41
|
name: def.name,
|
|
54
42
|
label: def.label,
|
|
@@ -66,16 +54,8 @@ export function adminSystemRoutes() {
|
|
|
66
54
|
});
|
|
67
55
|
});
|
|
68
56
|
/**
|
|
69
|
-
* What this deployment is and
|
|
70
|
-
*
|
|
71
|
-
* Answers the questions that otherwise need shell access: which version is
|
|
72
|
-
* running, which content model it was handed, how large the database has
|
|
73
|
-
* grown, how this deployment decides who may edit it.
|
|
74
|
-
*
|
|
75
|
-
* All of it goes to everybody who is admitted. There is no administrator
|
|
76
|
-
* role to withhold it from: admission is configuration, so the people who
|
|
77
|
-
* reach this endpoint are exactly the people the operator named. No secret
|
|
78
|
-
* value is returned anywhere, only whether one is set.
|
|
57
|
+
* What this deployment is and serves. Every admitted editor sees it, because
|
|
58
|
+
* there is no administrator role. It never returns a secret value.
|
|
79
59
|
*/
|
|
80
60
|
app.get("/system", async (c) => {
|
|
81
61
|
const ctx = c.get("ctx");
|
|
@@ -85,9 +65,8 @@ export function adminSystemRoutes() {
|
|
|
85
65
|
const files = await ctx.services.media.list();
|
|
86
66
|
const mediaBytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
87
67
|
const uptimeSeconds = Math.floor(process.uptime());
|
|
88
|
-
// The composition's own facts — database, authentication, admission —
|
|
89
|
-
// come from the app. The runtime cannot know them, which is the point.
|
|
90
68
|
const operational = ctx.operational();
|
|
69
|
+
const repository = await ctx.services.staging.repository();
|
|
91
70
|
return c.json({
|
|
92
71
|
cms: {
|
|
93
72
|
version: VERSION,
|
|
@@ -101,48 +80,34 @@ export function adminSystemRoutes() {
|
|
|
101
80
|
totals: stats.totals,
|
|
102
81
|
kinds: stats.kinds,
|
|
103
82
|
relations: stats.relations,
|
|
104
|
-
// Entry counts alongside the shape, because "8 fields, 0 entries" is
|
|
105
|
-
// the pair that tells an editor a collection was never filled in.
|
|
106
83
|
collections: stats.collections.map((item) => ({
|
|
107
84
|
...item,
|
|
108
85
|
entries: counts[item.name] ?? 0,
|
|
109
86
|
})),
|
|
110
87
|
singletons: stats.singletons,
|
|
111
88
|
},
|
|
112
|
-
/**
|
|
113
|
-
* Deliberately two separate things. `repository` is where the website's
|
|
114
|
-
* content lives and what must be backed up — by Git, which is doing it
|
|
115
|
-
* already. `database` is the composition's operational state, and null
|
|
116
|
-
* where the composition has none: the local editor keeps no database at
|
|
117
|
-
* all.
|
|
118
|
-
*/
|
|
119
89
|
repository: {
|
|
120
90
|
origin: ctx.site.contentRepository.origin,
|
|
121
91
|
entries: Object.values(counts).reduce((sum, count) => sum + count, 0),
|
|
122
92
|
media: { files: files.length, bytes: mediaBytes, readable: formatBytes(mediaBytes) },
|
|
93
|
+
...repository,
|
|
123
94
|
},
|
|
124
|
-
database: operational.database,
|
|
125
95
|
deployment: {
|
|
126
96
|
site: ctx.site.id,
|
|
127
97
|
baseUrl: config.MICA_PUBLIC_URL,
|
|
128
98
|
schemaOrigin: schemaOrigin(ctx.site.contentRepository),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// in `repository.origin` above.
|
|
132
|
-
contentRoot: config.REPOSITORY === "local" ? config.contentRoot : null,
|
|
99
|
+
// A clone that Mica keeps names its remote in `repository.url` instead.
|
|
100
|
+
contentRoot: repository.owner === "developer" ? config.contentRoot : null,
|
|
133
101
|
dataDir: operational.dataDir ?? null,
|
|
134
102
|
logLevel: config.LOG_LEVEL,
|
|
135
103
|
maxUploadBytes: config.MAX_UPLOAD_BYTES,
|
|
136
104
|
maxUploadReadable: formatBytes(config.MAX_UPLOAD_BYTES),
|
|
137
105
|
sessionTtlHours: operational.sessionTtlHours ?? null,
|
|
138
106
|
sessionIdleHours: operational.sessionIdleHours ?? null,
|
|
139
|
-
/** How this deployment authenticates, in words. */
|
|
140
107
|
auth: operational.auth,
|
|
141
|
-
/** What decides who may edit here, in the operator's own words. */
|
|
142
108
|
admission: operational.admission ?? null,
|
|
143
109
|
trustProxy: config.TRUST_PROXY,
|
|
144
110
|
adminOrigins: config.adminOrigins,
|
|
145
|
-
/** Names of the secrets this process generated for itself. */
|
|
146
111
|
generatedSecrets: operational.generatedSecrets ?? [],
|
|
147
112
|
memoryBytes: process.memoryUsage().rss,
|
|
148
113
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-system.js","sourceRoot":"","sources":["../../src/routes/admin-system.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin-system.js","sourceRoot":"","sources":["../../src/routes/admin-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Schema, dashboard and system information. The SPA knows no collection; it\n * renders what `/schema` returns.\n */\n\nimport { Hono } from \"hono\";\n\nimport { listedFields, resolveSlugField, resolveTitleField } from \"@micacms/core\";\n\nimport type { Env } from \"../http.js\";\nimport { schemaStats } from \"../lib/schema-stats.js\";\nimport { schemaOrigin } from \"../schema.js\";\nimport { VERSION } from \"../lib/version.js\";\nimport { formatBytes } from \"../services/media.js\";\n\nexport function adminSystemRoutes(): Hono<Env> {\n const app = new Hono<Env>();\n\n /** The content model, plus the derived values that the UI would otherwise recompute. */\n app.get(\"/schema\", (c) => {\n const { site } = c.get(\"ctx\");\n\n return c.json({\n collections: Object.values(site.model.collections).map((def) => ({\n name: def.name,\n label: def.label,\n labelSingular: def.labelSingular ?? def.label,\n description: def.description ?? null,\n sortable: def.sortable ?? false,\n defaultSort: def.defaultSort ?? null,\n titleField: resolveTitleField(def),\n slugField: resolveSlugField(def),\n listedFields: listedFields(def),\n fields: def.fields,\n })),\n singletons: Object.values(site.model.singletons).map((def) => ({\n name: def.name,\n label: def.label,\n description: def.description ?? null,\n fields: def.fields,\n })),\n });\n });\n\n app.get(\"/overview\", async (c) => {\n const ctx = c.get(\"ctx\");\n const { content, media } = ctx.services;\n\n const collections = await Promise.all(\n Object.values(ctx.site.model.collections).map(async (def) => ({\n name: def.name,\n label: def.label,\n total: (await content.list(def.name)).length,\n })),\n );\n\n const files = await media.list();\n\n return c.json({\n collections,\n media: {\n files: files.length,\n bytes: files.reduce((sum, file) => sum + file.size, 0),\n readable: formatBytes(files.reduce((sum, file) => sum + file.size, 0)),\n },\n repository: ctx.site.contentRepository.origin,\n });\n });\n\n /**\n * What this deployment is and serves. Every admitted editor sees it, because\n * there is no administrator role. It never returns a secret value.\n */\n app.get(\"/system\", async (c) => {\n const ctx = c.get(\"ctx\");\n const { config } = ctx;\n\n const stats = schemaStats(ctx.site.model);\n\n const counts = Object.fromEntries(\n await Promise.all(\n Object.values(ctx.site.model.collections).map(\n async (def) => [def.name, (await ctx.services.content.list(def.name)).length] as const,\n ),\n ),\n );\n\n const files = await ctx.services.media.list();\n const mediaBytes = files.reduce((sum, file) => sum + file.size, 0);\n\n const uptimeSeconds = Math.floor(process.uptime());\n\n const operational = ctx.operational();\n const repository = await ctx.services.staging.repository();\n\n return c.json({\n cms: {\n version: VERSION,\n environment: config.NODE_ENV,\n node: process.version,\n platform: `${process.platform}/${process.arch}`,\n startedAt: new Date(Date.now() - uptimeSeconds * 1000).toISOString(),\n uptimeSeconds,\n },\n schema: {\n totals: stats.totals,\n kinds: stats.kinds,\n relations: stats.relations,\n collections: stats.collections.map((item) => ({\n ...item,\n entries: counts[item.name] ?? 0,\n })),\n singletons: stats.singletons,\n },\n repository: {\n origin: ctx.site.contentRepository.origin,\n entries: Object.values(counts).reduce((sum, count) => sum + count, 0),\n media: { files: files.length, bytes: mediaBytes, readable: formatBytes(mediaBytes) },\n ...repository,\n },\n deployment: {\n site: ctx.site.id,\n baseUrl: config.MICA_PUBLIC_URL,\n schemaOrigin: schemaOrigin(ctx.site.contentRepository),\n // A clone that Mica keeps names its remote in `repository.url` instead.\n contentRoot: repository.owner === \"developer\" ? config.contentRoot : null,\n dataDir: operational.dataDir ?? null,\n logLevel: config.LOG_LEVEL,\n maxUploadBytes: config.MAX_UPLOAD_BYTES,\n maxUploadReadable: formatBytes(config.MAX_UPLOAD_BYTES),\n sessionTtlHours: operational.sessionTtlHours ?? null,\n sessionIdleHours: operational.sessionIdleHours ?? null,\n auth: operational.auth,\n admission: operational.admission ?? null,\n trustProxy: config.TRUST_PROXY,\n adminOrigins: config.adminOrigins,\n generatedSecrets: operational.generatedSecrets ?? [],\n memoryBytes: process.memoryUsage().rss,\n },\n });\n });\n\n return app;\n}\n"]}
|
package/dist/routes/health.d.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* a container that would not be any better for it.
|
|
6
|
-
* /ready the database, where the composition has one, and the content
|
|
7
|
-
* repository. Cached, because each probe spends one call of the
|
|
8
|
-
* repository token's quota and anybody can ask.
|
|
9
|
-
*
|
|
10
|
-
* Neither answer names the repository or the uptime: the endpoints are public,
|
|
11
|
-
* and the System view shows both to editors (#79).
|
|
2
|
+
* `/health` does no I/O, so a repository outage does not restart the container.
|
|
3
|
+
* `/ready` checks the content repository and is cached because it is public.
|
|
4
|
+
* Neither names the repository or the uptime.
|
|
12
5
|
*/
|
|
13
6
|
import { Hono } from "hono";
|
|
14
7
|
import type { Env } from "../http.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAWtC,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,aAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,CAmBnE"}
|
package/dist/routes/health.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* a container that would not be any better for it.
|
|
6
|
-
* /ready the database, where the composition has one, and the content
|
|
7
|
-
* repository. Cached, because each probe spends one call of the
|
|
8
|
-
* repository token's quota and anybody can ask.
|
|
9
|
-
*
|
|
10
|
-
* Neither answer names the repository or the uptime: the endpoints are public,
|
|
11
|
-
* and the System view shows both to editors (#79).
|
|
2
|
+
* `/health` does no I/O, so a repository outage does not restart the container.
|
|
3
|
+
* `/ready` checks the content repository and is cached because it is public.
|
|
4
|
+
* Neither names the repository or the uptime.
|
|
12
5
|
*/
|
|
13
6
|
import { Hono } from "hono";
|
|
14
7
|
export function healthRoutes(options = {}) {
|
|
@@ -30,21 +23,12 @@ export function healthRoutes(options = {}) {
|
|
|
30
23
|
async function probe(c) {
|
|
31
24
|
const ctx = c.get("ctx");
|
|
32
25
|
const logger = c.get("logger");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
ctx.health.database();
|
|
39
|
-
database = "ok";
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
logger.error("Health check failed", { error: String(error) });
|
|
43
|
-
database = "unavailable";
|
|
44
|
-
}
|
|
26
|
+
// A managed clone knows whether it can be edited without asking the remote.
|
|
27
|
+
if (ctx.health.ready) {
|
|
28
|
+
const { ok, reasons } = ctx.health.ready();
|
|
29
|
+
return { status: ok ? "ok" : "error", repository: ok ? "ok" : "unavailable", reasons };
|
|
45
30
|
}
|
|
46
|
-
// `list` on a missing root returns an empty array
|
|
47
|
-
// this proves reachability, not that anyone has written content yet.
|
|
31
|
+
// `list` on a missing root returns an empty array, so this proves reachability only.
|
|
48
32
|
let repository = "ok";
|
|
49
33
|
try {
|
|
50
34
|
await ctx.site.contentRepository.list("");
|
|
@@ -53,7 +37,6 @@ async function probe(c) {
|
|
|
53
37
|
logger.error("Content repository is unreachable", { error: String(error) });
|
|
54
38
|
repository = "unavailable";
|
|
55
39
|
}
|
|
56
|
-
|
|
57
|
-
return { status: ok ? "ok" : "error", ...(database ? { database } : {}), repository };
|
|
40
|
+
return { status: repository === "ok" ? "ok" : "error", repository };
|
|
58
41
|
}
|
|
59
42
|
//# sourceMappingURL=health.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAmB1C,MAAM,UAAU,YAAY,CAAC,UAAyB,EAAE;IACtD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,6EAA6E;IAC7E,IAAI,MAAM,GAAsD,IAAI,CAAC;IAErE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAO,CAAC;IAE5B,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEpD,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC5B,IAAI,CAAC,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,OAAO,EAAE,CAAC;YAC5C,MAAM,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;QACnC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,CAAe;IAClC,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAE/B,4EAA4E;IAC5E,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC3C,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,CAAC;IACzF,CAAC;IAED,qFAAqF;IACrF,IAAI,UAAU,GAAU,IAAI,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC5E,UAAU,GAAG,aAAa,CAAC;IAC7B,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;AACtE,CAAC","sourcesContent":["/**\n * `/health` does no I/O, so a repository outage does not restart the container.\n * `/ready` checks the content repository and is cached because it is public.\n * Neither names the repository or the uptime.\n */\n\nimport { Hono, type Context } from \"hono\";\n\nimport type { Env } from \"../http.js\";\n\ntype Check = \"ok\" | \"unavailable\";\n\ninterface Readiness {\n status: \"ok\" | \"error\";\n repository: Check;\n /** Short codes from the composition, such as `sync_failing`. */\n reasons?: string[];\n}\n\nexport interface HealthOptions {\n /** How long a readiness result is reused. */\n cacheMs?: number;\n now?: () => number;\n}\n\nexport function healthRoutes(options: HealthOptions = {}): Hono<Env> {\n const cacheMs = options.cacheMs ?? 30_000;\n const now = options.now ?? Date.now;\n // The promise, not the result: requests that arrive during a probe share it.\n let cached: { at: number; result: Promise<Readiness> } | null = null;\n\n const app = new Hono<Env>();\n\n app.get(\"/health\", (c) => c.json({ status: \"ok\" }));\n\n app.get(\"/ready\", async (c) => {\n if (!cached || now() - cached.at >= cacheMs) {\n cached = { at: now(), result: probe(c) };\n }\n const result = await cached.result;\n return c.json(result, result.status === \"ok\" ? 200 : 503);\n });\n\n return app;\n}\n\nasync function probe(c: Context<Env>): Promise<Readiness> {\n const ctx = c.get(\"ctx\");\n const logger = c.get(\"logger\");\n\n // A managed clone knows whether it can be edited without asking the remote.\n if (ctx.health.ready) {\n const { ok, reasons } = ctx.health.ready();\n return { status: ok ? \"ok\" : \"error\", repository: ok ? \"ok\" : \"unavailable\", reasons };\n }\n\n // `list` on a missing root returns an empty array, so this proves reachability only.\n let repository: Check = \"ok\";\n try {\n await ctx.site.contentRepository.list(\"\");\n } catch (error) {\n logger.error(\"Content repository is unreachable\", { error: String(error) });\n repository = \"unavailable\";\n }\n\n return { status: repository === \"ok\" ? \"ok\" : \"error\", repository };\n}\n"]}
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* repository, at `.mica/schema.json`, through the same connector it reads
|
|
6
|
-
* entries with. The Astro project writes that file and the developer commits
|
|
7
|
-
* it, so the model reaches the server the way content does — no artifact to
|
|
8
|
-
* mount, no second channel to keep in step, and a model change that is
|
|
9
|
-
* reviewable in a diff.
|
|
10
|
-
*
|
|
11
|
-
* That is what lets one published image serve any project: the model is plain
|
|
12
|
-
* data (field descriptors hold no functions, so `JSON.stringify(site)`
|
|
13
|
-
* round-trips), and every consumer downstream — form generation, payload
|
|
14
|
-
* validation, the admin — already reads it out of the request context.
|
|
15
|
-
*
|
|
16
|
-
* Whatever arrives is re-validated as if it had just been written by hand. A
|
|
17
|
-
* manifest from a repository is untrusted input: it may have been produced by a
|
|
18
|
-
* different version of `@micacms/core`, hand-edited, or truncated.
|
|
2
|
+
* Reads the content model from `.mica/schema.json` in the content repository.
|
|
3
|
+
* The Astro project writes it. The manifest is untrusted input (another core
|
|
4
|
+
* version, hand-edited or truncated), so it is validated again here.
|
|
19
5
|
*/
|
|
20
6
|
import { type ContentSource, type Site } from "@micacms/core";
|
|
21
7
|
export declare class SchemaError extends Error {
|
|
@@ -24,33 +10,19 @@ export declare class SchemaError extends Error {
|
|
|
24
10
|
/** Where a model came from, for logs and the system screen. */
|
|
25
11
|
export declare function schemaOrigin(source: ContentSource): string;
|
|
26
12
|
/**
|
|
27
|
-
* Reads the model the content repository
|
|
28
|
-
*
|
|
29
|
-
* A missing file is the interesting failure and gets the long message: it is
|
|
30
|
-
* what a first deployment hits, and the fix — build the Astro project, commit
|
|
31
|
-
* the result — is not guessable from "not found".
|
|
13
|
+
* Reads the model from the content repository. A missing file gets a long
|
|
14
|
+
* message because a first deployment hits it.
|
|
32
15
|
*/
|
|
33
16
|
export declare function loadSchema(source: ContentSource): Promise<Site>;
|
|
34
17
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `defineSite` assumes its input came from the typed builders, so it never
|
|
39
|
-
* questions a `kind`. Field kinds reach a `switch` with no default when a form
|
|
40
|
-
* is rendered and when a payload is validated, so an unknown one surfaces as an
|
|
41
|
-
* incoherent failure much later, on an editor's screen. Catching it here turns
|
|
42
|
-
* a version mismatch between the project's `@micacms/core` and the image's
|
|
43
|
-
* into a startup error that names the field.
|
|
18
|
+
* Runs the `defineSite` checks, and also refuses an unknown field `kind`, which
|
|
19
|
+
* `defineSite` does not check. This turns a core version mismatch into a
|
|
20
|
+
* startup error, not a later failure on an editor's screen.
|
|
44
21
|
*/
|
|
45
22
|
export declare function checkSchema(raw: unknown, origin?: string): Site;
|
|
46
23
|
/**
|
|
47
|
-
* A
|
|
48
|
-
*
|
|
49
|
-
* Logged at startup and returned by the schema endpoint so that a website built
|
|
50
|
-
* against one model and a CMS serving another is a visible mismatch rather than
|
|
51
|
-
* a puzzling absence of fields. Key order follows the model's own declaration
|
|
52
|
-
* order, which is meaningful here — reordering fields reorders the admin form,
|
|
53
|
-
* so it is a real change and should produce a different digest.
|
|
24
|
+
* A short digest of a model, to show drift. Field order counts, because it is
|
|
25
|
+
* the order of the admin form.
|
|
54
26
|
*/
|
|
55
27
|
export declare function schemaFingerprint(site: Site): Promise<string>;
|
|
56
28
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAQL,KAAK,aAAa,EAGlB,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAEvB,qBAAa,WAAY,SAAQ,KAAK;IAC3B,IAAI,SAAiB;CAC/B;AAED,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE1D;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BrE;AAWD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,SAAe,GAAG,IAAI,CAiDrE;AA0BD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnE"}
|
package/dist/schema.js
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* repository, at `.mica/schema.json`, through the same connector it reads
|
|
6
|
-
* entries with. The Astro project writes that file and the developer commits
|
|
7
|
-
* it, so the model reaches the server the way content does — no artifact to
|
|
8
|
-
* mount, no second channel to keep in step, and a model change that is
|
|
9
|
-
* reviewable in a diff.
|
|
10
|
-
*
|
|
11
|
-
* That is what lets one published image serve any project: the model is plain
|
|
12
|
-
* data (field descriptors hold no functions, so `JSON.stringify(site)`
|
|
13
|
-
* round-trips), and every consumer downstream — form generation, payload
|
|
14
|
-
* validation, the admin — already reads it out of the request context.
|
|
15
|
-
*
|
|
16
|
-
* Whatever arrives is re-validated as if it had just been written by hand. A
|
|
17
|
-
* manifest from a repository is untrusted input: it may have been produced by a
|
|
18
|
-
* different version of `@micacms/core`, hand-edited, or truncated.
|
|
2
|
+
* Reads the content model from `.mica/schema.json` in the content repository.
|
|
3
|
+
* The Astro project writes it. The manifest is untrusted input (another core
|
|
4
|
+
* version, hand-edited or truncated), so it is validated again here.
|
|
19
5
|
*/
|
|
20
6
|
import { checkCompatibility, defineSite, isFieldKind, isProtocolManifest, MANIFEST_PATH, PROTOCOL_VERSION, } from "@micacms/core";
|
|
21
7
|
export class SchemaError extends Error {
|
|
@@ -26,11 +12,8 @@ export function schemaOrigin(source) {
|
|
|
26
12
|
return `${source.origin}/${MANIFEST_PATH}`;
|
|
27
13
|
}
|
|
28
14
|
/**
|
|
29
|
-
* Reads the model the content repository
|
|
30
|
-
*
|
|
31
|
-
* A missing file is the interesting failure and gets the long message: it is
|
|
32
|
-
* what a first deployment hits, and the fix — build the Astro project, commit
|
|
33
|
-
* the result — is not guessable from "not found".
|
|
15
|
+
* Reads the model from the content repository. A missing file gets a long
|
|
16
|
+
* message because a first deployment hits it.
|
|
34
17
|
*/
|
|
35
18
|
export async function loadSchema(source) {
|
|
36
19
|
const origin = schemaOrigin(source);
|
|
@@ -39,8 +22,7 @@ export async function loadSchema(source) {
|
|
|
39
22
|
text = await source.read(MANIFEST_PATH);
|
|
40
23
|
}
|
|
41
24
|
catch (error) {
|
|
42
|
-
//
|
|
43
|
-
// because the two have completely different fixes.
|
|
25
|
+
// An unreachable repository is not a bad model; the fixes are different.
|
|
44
26
|
throw new SchemaError(`Could not read the content model from ${source.origin}.\n` +
|
|
45
27
|
` ${error.message}\n` +
|
|
46
28
|
` The repository has to be reachable before Mica can start: the model\n` +
|
|
@@ -64,23 +46,15 @@ function describeMissing(origin) {
|
|
|
64
46
|
` then commit ${MANIFEST_PATH} and push it to the branch Mica is reading.`);
|
|
65
47
|
}
|
|
66
48
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* `defineSite` assumes its input came from the typed builders, so it never
|
|
71
|
-
* questions a `kind`. Field kinds reach a `switch` with no default when a form
|
|
72
|
-
* is rendered and when a payload is validated, so an unknown one surfaces as an
|
|
73
|
-
* incoherent failure much later, on an editor's screen. Catching it here turns
|
|
74
|
-
* a version mismatch between the project's `@micacms/core` and the image's
|
|
75
|
-
* into a startup error that names the field.
|
|
49
|
+
* Runs the `defineSite` checks, and also refuses an unknown field `kind`, which
|
|
50
|
+
* `defineSite` does not check. This turns a core version mismatch into a
|
|
51
|
+
* startup error, not a later failure on an editor's screen.
|
|
76
52
|
*/
|
|
77
53
|
export function checkSchema(raw, origin = "the schema") {
|
|
78
54
|
if (raw === null || typeof raw !== "object") {
|
|
79
55
|
throw new SchemaError(`${origin} is ${raw === null ? "null" : typeof raw}, expected an object.`);
|
|
80
56
|
}
|
|
81
|
-
//
|
|
82
|
-
// the protocol version is a backstop for changes that cannot be detected by
|
|
83
|
-
// looking, not an admission ticket.
|
|
57
|
+
// A bare `{ collections }` without the protocol envelope is also accepted.
|
|
84
58
|
if (isProtocolManifest(raw)) {
|
|
85
59
|
const compatibility = checkCompatibility(raw);
|
|
86
60
|
if (!compatibility.ok && compatibility.reason === "newer") {
|
|
@@ -93,10 +67,7 @@ export function checkSchema(raw, origin = "the schema") {
|
|
|
93
67
|
if (candidate.collections === null || typeof candidate.collections !== "object") {
|
|
94
68
|
throw new SchemaError(`${origin} has no \`collections\`. Expected defineSite({ collections: { ... } }).`);
|
|
95
69
|
}
|
|
96
|
-
|
|
97
|
-
// outlives the release that produced it, so both spellings are read here;
|
|
98
|
-
// only the new one is written anywhere.
|
|
99
|
-
const rawSingletons = candidate.singletons ?? candidate.globals;
|
|
70
|
+
const rawSingletons = candidate.singletons;
|
|
100
71
|
if (rawSingletons !== undefined && (rawSingletons === null || typeof rawSingletons !== "object")) {
|
|
101
72
|
throw new SchemaError(`${origin} has a \`singletons\` that is not an object.`);
|
|
102
73
|
}
|
|
@@ -108,29 +79,12 @@ export function checkSchema(raw, origin = "the schema") {
|
|
|
108
79
|
for (const [name, def] of Object.entries(singletons)) {
|
|
109
80
|
assertFieldMap(def?.fields, `singletons.${name}`, origin);
|
|
110
81
|
}
|
|
111
|
-
|
|
112
|
-
* A default sort naming a field the collection does not declare is dropped
|
|
113
|
-
* here rather than refused.
|
|
114
|
-
*
|
|
115
|
-
* `defineSite` rejects it, which is right when somebody is writing the model
|
|
116
|
-
* and can fix the line they are looking at. By the time it reaches Mica the
|
|
117
|
-
* model is committed and a site is starting against it: the entries are
|
|
118
|
-
* intact, the fields are intact, and the only casualty is the order of a
|
|
119
|
-
* list. Refusing to serve the site over that would be out of proportion —
|
|
120
|
-
* and models written before the check existed contain exactly this, because
|
|
121
|
-
* the old fixed list of sort fields offered names no collection had.
|
|
122
|
-
*
|
|
123
|
-
* So the option is dropped, the list falls back to the order editors
|
|
124
|
-
* arranged by hand, and the project's next build reports it properly.
|
|
125
|
-
*/
|
|
82
|
+
// Drop, do not refuse, a default sort on an undeclared field: only the list order is affected.
|
|
126
83
|
for (const def of Object.values(collections)) {
|
|
127
84
|
const field = def?.defaultSort?.field;
|
|
128
85
|
if (field && field !== "position" && !def.fields?.[field])
|
|
129
86
|
delete def.defaultSort;
|
|
130
87
|
}
|
|
131
|
-
// Name injection and every cross-reference check — unknown relation targets,
|
|
132
|
-
// slugs derived from absent siblings, reserved collection names — come from
|
|
133
|
-
// the same function a hand-written model goes through.
|
|
134
88
|
try {
|
|
135
89
|
return defineSite({ collections, singletons });
|
|
136
90
|
}
|
|
@@ -158,13 +112,8 @@ function assertFieldMap(fields, at, origin) {
|
|
|
158
112
|
}
|
|
159
113
|
}
|
|
160
114
|
/**
|
|
161
|
-
* A
|
|
162
|
-
*
|
|
163
|
-
* Logged at startup and returned by the schema endpoint so that a website built
|
|
164
|
-
* against one model and a CMS serving another is a visible mismatch rather than
|
|
165
|
-
* a puzzling absence of fields. Key order follows the model's own declaration
|
|
166
|
-
* order, which is meaningful here — reordering fields reorders the admin form,
|
|
167
|
-
* so it is a real change and should produce a different digest.
|
|
115
|
+
* A short digest of a model, to show drift. Field order counts, because it is
|
|
116
|
+
* the order of the admin form.
|
|
168
117
|
*/
|
|
169
118
|
export async function schemaFingerprint(site) {
|
|
170
119
|
const { createHash } = await import("node:crypto");
|