@micacms/runtime 0.5.0 → 0.6.0-rc.1

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.
Files changed (87) hide show
  1. package/README.md +3 -1
  2. package/dist/app.js +1 -1
  3. package/dist/app.js.map +1 -1
  4. package/dist/config.d.ts +13 -13
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +9 -9
  7. package/dist/config.js.map +1 -1
  8. package/dist/context.js +2 -2
  9. package/dist/context.js.map +1 -1
  10. package/dist/http.d.ts +1 -3
  11. package/dist/http.d.ts.map +1 -1
  12. package/dist/http.js +2 -9
  13. package/dist/http.js.map +1 -1
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/errors.d.ts +1 -2
  19. package/dist/lib/errors.d.ts.map +1 -1
  20. package/dist/lib/errors.js +0 -2
  21. package/dist/lib/errors.js.map +1 -1
  22. package/dist/lib/id.d.ts +0 -10
  23. package/dist/lib/id.d.ts.map +1 -1
  24. package/dist/lib/id.js +0 -50
  25. package/dist/lib/id.js.map +1 -1
  26. package/dist/lib/rate-limit.d.ts +0 -2
  27. package/dist/lib/rate-limit.d.ts.map +1 -1
  28. package/dist/lib/rate-limit.js +0 -3
  29. package/dist/lib/rate-limit.js.map +1 -1
  30. package/dist/middleware/index.d.ts +1 -1
  31. package/dist/middleware/index.js +2 -2
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/routes/admin-content.d.ts.map +1 -1
  34. package/dist/routes/admin-content.js +3 -2
  35. package/dist/routes/admin-content.js.map +1 -1
  36. package/dist/routes/admin-system.js +4 -4
  37. package/dist/routes/admin-system.js.map +1 -1
  38. package/dist/schema.d.ts +6 -0
  39. package/dist/schema.d.ts.map +1 -1
  40. package/dist/schema.js +19 -1
  41. package/dist/schema.js.map +1 -1
  42. package/dist/services/changes.d.ts.map +1 -1
  43. package/dist/services/changes.js +89 -13
  44. package/dist/services/changes.js.map +1 -1
  45. package/dist/services/content-store.d.ts.map +1 -1
  46. package/dist/services/content-store.js +9 -10
  47. package/dist/services/content-store.js.map +1 -1
  48. package/dist/services/staging.d.ts.map +1 -1
  49. package/dist/services/staging.js +7 -4
  50. package/dist/services/staging.js.map +1 -1
  51. package/dist/workspace/tree.d.ts +6 -0
  52. package/dist/workspace/tree.d.ts.map +1 -1
  53. package/dist/workspace/tree.js +28 -0
  54. package/dist/workspace/tree.js.map +1 -1
  55. package/package.json +12 -4
  56. package/src/app.ts +127 -0
  57. package/src/config.ts +113 -0
  58. package/src/context.ts +167 -0
  59. package/src/http.ts +46 -0
  60. package/src/index.ts +141 -0
  61. package/src/lib/errors.ts +84 -0
  62. package/src/lib/file-type.ts +108 -0
  63. package/src/lib/id.ts +6 -0
  64. package/src/lib/logger.ts +87 -0
  65. package/src/lib/ports.ts +17 -0
  66. package/src/lib/rate-limit.ts +72 -0
  67. package/src/lib/schema-stats.ts +183 -0
  68. package/src/lib/version.ts +29 -0
  69. package/src/middleware/index.ts +234 -0
  70. package/src/routes/admin-content.ts +197 -0
  71. package/src/routes/admin-media.ts +96 -0
  72. package/src/routes/admin-spa.ts +82 -0
  73. package/src/routes/admin-staging.ts +126 -0
  74. package/src/routes/admin-system.ts +146 -0
  75. package/src/routes/health.ts +67 -0
  76. package/src/schema.ts +217 -0
  77. package/src/server.ts +68 -0
  78. package/src/services/changes.ts +605 -0
  79. package/src/services/content-store.ts +492 -0
  80. package/src/services/media.ts +229 -0
  81. package/src/services/staging.ts +330 -0
  82. package/src/workspace/developer.ts +46 -0
  83. package/src/workspace/files.ts +243 -0
  84. package/src/workspace/git.ts +108 -0
  85. package/src/workspace/locks.ts +103 -0
  86. package/src/workspace/policy.ts +77 -0
  87. package/src/workspace/tree.ts +314 -0
@@ -14,8 +14,6 @@ export interface RateLimitResult {
14
14
  }
15
15
  export interface RateLimiter {
16
16
  check(key: string): RateLimitResult;
17
- /** Forgets a key — called after a successful login. */
18
- reset(key: string): void;
19
17
  size(): number;
20
18
  stop(): void;
21
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/lib/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,uDAAuD;IACvD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,IAAI,MAAM,CAAC;IACf,IAAI,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CA8C1E"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/lib/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,IAAI,IAAI,MAAM,CAAC;IACf,IAAI,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CA0C1E"}
@@ -32,9 +32,6 @@ export function createRateLimiter(options) {
32
32
  }
33
33
  return { allowed: true, remaining: options.limit - bucket.count, retryAfter };
34
34
  },
35
- reset(key) {
36
- buckets.delete(key);
37
- },
38
35
  size() {
39
36
  return buckets.size;
40
37
  },
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/lib/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwBH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8C,CAAC;IAEtE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACpC,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG;gBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAE1D,6DAA6D;IAC7D,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAEhB,OAAO;QACL,KAAK,CAAC,GAAG;YACP,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAEhC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;gBACrC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAChE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YACxE,CAAC;YAED,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;YAE5D,IAAI,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;gBACjC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;YACtD,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC;QAChF,CAAC;QAED,KAAK,CAAC,GAAG;YACP,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;QAED,IAAI;YACF,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;QAED,IAAI;YACF,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Fixed-window rate limiting, in memory.\n *\n * The CMS is a single process by design, so an in-memory counter is\n * sufficient and avoids a Redis dependency. The trade-off is deliberate:\n * counters reset on restart. Mica checks no passwords; the identity provider\n * does, with its own limits, so a restart-and-retry loop gains little.\n */\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n /** Seconds until the window resets. */\n retryAfter: number;\n}\n\nexport interface RateLimiter {\n check(key: string): RateLimitResult;\n /** Forgets a key — called after a successful login. */\n reset(key: string): void;\n size(): number;\n stop(): void;\n}\n\nexport interface RateLimiterOptions {\n limit: number;\n windowMs: number;\n /** How often expired buckets are swept. */\n sweepMs?: number;\n}\n\nexport function createRateLimiter(options: RateLimiterOptions): RateLimiter {\n const buckets = new Map<string, { count: number; resetAt: number }>();\n\n const sweep = setInterval(() => {\n const now = Date.now();\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) buckets.delete(key);\n }\n }, options.sweepMs ?? Math.max(options.windowMs, 60_000));\n\n // A sweep timer must not keep the process alive on shutdown.\n sweep.unref?.();\n\n return {\n check(key) {\n const now = Date.now();\n const bucket = buckets.get(key);\n\n if (!bucket || bucket.resetAt <= now) {\n buckets.set(key, { count: 1, resetAt: now + options.windowMs });\n return { allowed: true, remaining: options.limit - 1, retryAfter: 0 };\n }\n\n bucket.count++;\n const retryAfter = Math.ceil((bucket.resetAt - now) / 1000);\n\n if (bucket.count > options.limit) {\n return { allowed: false, remaining: 0, retryAfter };\n }\n\n return { allowed: true, remaining: options.limit - bucket.count, retryAfter };\n },\n\n reset(key) {\n buckets.delete(key);\n },\n\n size() {\n return buckets.size;\n },\n\n stop() {\n clearInterval(sweep);\n buckets.clear();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/lib/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsBH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8C,CAAC;IAEtE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACpC,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG;gBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAE1D,6DAA6D;IAC7D,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAEhB,OAAO;QACL,KAAK,CAAC,GAAG;YACP,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAEhC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;gBACrC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAChE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YACxE,CAAC;YAED,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;YAE5D,IAAI,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;gBACjC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;YACtD,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC;QAChF,CAAC;QAED,IAAI;YACF,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;QAED,IAAI;YACF,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Fixed-window rate limiting, in memory.\n *\n * The CMS is a single process by design, so an in-memory counter is\n * sufficient and avoids a Redis dependency. The trade-off is deliberate:\n * counters reset on restart. Mica checks no passwords; the identity provider\n * does, with its own limits, so a restart-and-retry loop gains little.\n */\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n /** Seconds until the window resets. */\n retryAfter: number;\n}\n\nexport interface RateLimiter {\n check(key: string): RateLimitResult;\n size(): number;\n stop(): void;\n}\n\nexport interface RateLimiterOptions {\n limit: number;\n windowMs: number;\n /** How often expired buckets are swept. */\n sweepMs?: number;\n}\n\nexport function createRateLimiter(options: RateLimiterOptions): RateLimiter {\n const buckets = new Map<string, { count: number; resetAt: number }>();\n\n const sweep = setInterval(() => {\n const now = Date.now();\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) buckets.delete(key);\n }\n }, options.sweepMs ?? Math.max(options.windowMs, 60_000));\n\n // A sweep timer must not keep the process alive on shutdown.\n sweep.unref?.();\n\n return {\n check(key) {\n const now = Date.now();\n const bucket = buckets.get(key);\n\n if (!bucket || bucket.resetAt <= now) {\n buckets.set(key, { count: 1, resetAt: now + options.windowMs });\n return { allowed: true, remaining: options.limit - 1, retryAfter: 0 };\n }\n\n bucket.count++;\n const retryAfter = Math.ceil((bucket.resetAt - now) / 1000);\n\n if (bucket.count > options.limit) {\n return { allowed: false, remaining: 0, retryAfter };\n }\n\n return { allowed: true, remaining: options.limit - bucket.count, retryAfter };\n },\n\n size() {\n return buckets.size;\n },\n\n stop() {\n clearInterval(sweep);\n buckets.clear();\n },\n };\n}\n"]}
@@ -25,7 +25,7 @@ export declare const MAX_FILES_PER_UPLOAD = 10;
25
25
  export declare const MAX_JSON_BYTES: number;
26
26
  /**
27
27
  * Refuses an oversized body before a handler reads it into memory (#148). An
28
- * upload may carry `MAX_FILES_PER_UPLOAD` files of `MAX_UPLOAD_BYTES`, plus
28
+ * upload may carry `MAX_FILES_PER_UPLOAD` files of `MICA_MAX_UPLOAD_BYTES`, plus
29
29
  * room for the multipart framing; every other request gets `MAX_JSON_BYTES`.
30
30
  */
31
31
  export declare function requestSizeLimit(): MiddlewareHandler<Env>;
@@ -86,7 +86,7 @@ export const MAX_FILES_PER_UPLOAD = 10;
86
86
  export const MAX_JSON_BYTES = 2 * 1024 * 1024;
87
87
  /**
88
88
  * Refuses an oversized body before a handler reads it into memory (#148). An
89
- * upload may carry `MAX_FILES_PER_UPLOAD` files of `MAX_UPLOAD_BYTES`, plus
89
+ * upload may carry `MAX_FILES_PER_UPLOAD` files of `MICA_MAX_UPLOAD_BYTES`, plus
90
90
  * room for the multipart framing; every other request gets `MAX_JSON_BYTES`.
91
91
  */
92
92
  export function requestSizeLimit() {
@@ -94,7 +94,7 @@ export function requestSizeLimit() {
94
94
  const { config } = c.get("ctx");
95
95
  const upload = c.req.method === "POST" && c.req.path === "/api/admin/media";
96
96
  const maxSize = upload
97
- ? config.MAX_UPLOAD_BYTES * MAX_FILES_PER_UPLOAD + 1024 * 1024
97
+ ? config.MICA_MAX_UPLOAD_BYTES * MAX_FILES_PER_UPLOAD + 1024 * 1024
98
98
  : MAX_JSON_BYTES;
99
99
  return bodyLimit({
100
100
  maxSize,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Middleware.\n *\n * Ordering matters and is asserted in `app.ts`:\n * requestContext → guards → securityHeaders → errorBoundary → rateLimit\n * → originGuard → the auth surface's gate → route\n *\n * The gate itself is not here. Whether a request is anybody — a session\n * middleware, `allowAll()` — belongs to the composing app, which is what\n * keeps this package authentication-neutral.\n */\n\nimport { ContentPathError } from \"@micacms/core\";\nimport type { ErrorHandler, MiddlewareHandler } from \"hono\";\nimport { bodyLimit } from \"hono/body-limit\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\n\nimport { ApiError, badRequest } from \"../lib/errors.js\";\nimport { clientIp, type Env } from \"../http.js\";\nimport { randomToken } from \"../lib/id.js\";\n\n/** Attaches the container, a request id and a scoped logger; logs the result. */\nexport function requestContext(ctx: Env[\"Variables\"][\"ctx\"]): MiddlewareHandler<Env> {\n return async (c, next) => {\n // A client's id is logged and echoed, so only a short, plain one is kept (#150).\n const offered = c.req.header(\"x-request-id\");\n const requestId = offered && /^[A-Za-z0-9_-]{1,64}$/.test(offered) ? offered : randomToken(8);\n const logger = ctx.logger.child({ requestId });\n\n c.set(\"ctx\", ctx);\n c.set(\"requestId\", requestId);\n c.set(\"logger\", logger);\n c.header(\"x-request-id\", requestId);\n\n const startedAt = performance.now();\n await next();\n const duration = Math.round(performance.now() - startedAt);\n\n const line = {\n method: c.req.method,\n path: new URL(c.req.url).pathname,\n status: c.res.status,\n duration,\n };\n\n /**\n * \"Am I signed in?\" — \"No.\"\n *\n * `/auth/me` is the SPA's first request on every page load, and a 401 is\n * one of its two correct answers, not a rejection worth a warning. Logging\n * it as one puts a scary line in the terminal of everybody who has simply\n * opened the editor while signed out.\n */\n const expected401 = c.res.status === 401 && line.path === \"/api/auth/me\";\n\n if (c.res.status >= 500) logger.error(\"Request failed\", line);\n else if (c.res.status >= 400 && !expected401) logger.warn(\"Request rejected\", line);\n else logger.debug(\"Request\", line);\n };\n}\n\n/**\n * Same-origin only. `blob:` and `data:` images cover previews; a route that\n * needs a stricter policy, such as the sandbox on media files, sets its own.\n */\nexport const CONTENT_SECURITY_POLICY = [\n \"default-src 'self'\",\n \"img-src 'self' blob: data:\",\n \"object-src 'none'\",\n \"base-uri 'none'\",\n \"frame-ancestors 'none'\",\n].join(\"; \");\n\nexport function securityHeaders(): MiddlewareHandler<Env> {\n return async (c, next) => {\n await next();\n\n if (!c.res.headers.has(\"content-security-policy\")) {\n c.header(\"content-security-policy\", CONTENT_SECURITY_POLICY);\n }\n // Only for an https public URL: HSTS over plain HTTP is ignored, and on\n // localhost it would pin a developer's other projects to https.\n if (c.get(\"ctx\").config.MICA_PUBLIC_URL.startsWith(\"https://\")) {\n c.header(\"strict-transport-security\", \"max-age=31536000\");\n }\n\n c.header(\"x-content-type-options\", \"nosniff\");\n c.header(\"referrer-policy\", \"same-origin\");\n c.header(\"x-frame-options\", \"DENY\");\n // The admin is a first-party SPA; nothing here needs to be embedded.\n c.header(\"permissions-policy\", \"geolocation=(), microphone=(), camera=()\");\n };\n}\n\n/** Files in one upload request; the Media view sends a selection in one request. */\nexport const MAX_FILES_PER_UPLOAD = 10;\n\n/** Far more than any entry's JSON, and small enough to refuse a flood early. */\nexport const MAX_JSON_BYTES = 2 * 1024 * 1024;\n\n/**\n * Refuses an oversized body before a handler reads it into memory (#148). An\n * upload may carry `MAX_FILES_PER_UPLOAD` files of `MAX_UPLOAD_BYTES`, plus\n * room for the multipart framing; every other request gets `MAX_JSON_BYTES`.\n */\nexport function requestSizeLimit(): MiddlewareHandler<Env> {\n return async (c, next) => {\n const { config } = c.get(\"ctx\");\n const upload = c.req.method === \"POST\" && c.req.path === \"/api/admin/media\";\n const maxSize = upload\n ? config.MAX_UPLOAD_BYTES * MAX_FILES_PER_UPLOAD + 1024 * 1024\n : MAX_JSON_BYTES;\n\n return bodyLimit({\n maxSize,\n onError: (ctx) => {\n const refused = new ApiError(\n \"payload_too_large\",\n upload\n ? \"The upload is too large. Upload fewer or smaller files at a time.\"\n : \"The request is too large.\",\n );\n return ctx.json(refused.toBody(), 413);\n },\n })(c, next);\n };\n}\n\n/**\n * Converts thrown errors into the API error envelope. Deliberate failures\n * (`ApiError`) are reported as-is; anything else is a bug and becomes a bare\n * 500, with the stack kept in the log rather than sent to the client.\n *\n * Registered with `app.onError`, not as middleware: Hono's `compose` wraps each\n * handler individually and routes a throw to `onError` at that level, so a\n * try/catch in an outer middleware would never see it.\n */\nexport function errorHandler(): ErrorHandler<Env> {\n return (error, c) => {\n const logger = c.get(\"logger\") ?? c.get(\"ctx\").logger;\n\n if (error instanceof ApiError) {\n logger.debug(\"Handled error\", { code: error.code, message: error.message });\n return c.json(error.toBody(), error.status as ContentfulStatusCode, error.headers);\n }\n\n // A path that could leave the content root is the client's mistake, not a bug.\n if (error instanceof ContentPathError) {\n logger.warn(\"Refused content path\", { message: error.message });\n const refused = badRequest(error.message);\n return c.json(refused.toBody(), refused.status as ContentfulStatusCode);\n }\n\n logger.error(\"Unhandled error\", {\n error: error instanceof Error ? (error.stack ?? error.message) : String(error),\n path: new URL(c.req.url).pathname,\n });\n\n return c.json(\n {\n error: {\n code: \"internal_error\" as const,\n message: \"Something went wrong. Please try again.\",\n requestId: c.get(\"requestId\"),\n },\n },\n 500,\n );\n };\n}\n\n/** Coarse per-IP limit, applied to the whole API surface. */\nexport function rateLimit(): MiddlewareHandler<Env> {\n return async (c, next) => {\n const { limiters } = c.get(\"ctx\");\n const result = limiters.api.check(clientIp(c));\n\n if (!result.allowed) {\n throw new ApiError(\"rate_limited\", \"Too many requests. Please slow down.\", {\n headers: { \"retry-after\": String(result.retryAfter) },\n });\n }\n\n await next();\n };\n}\n\nconst SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\nconst normaliseOrigin = (value: string): string => {\n try {\n return new URL(value).origin;\n } catch {\n return value;\n }\n};\n\nexport function allowedOrigins(config: {\n MICA_PUBLIC_URL: string;\n adminOrigins: string[];\n}): string[] {\n return [...new Set([config.MICA_PUBLIC_URL, ...config.adminOrigins].map(normaliseOrigin))];\n}\n\n/**\n * First CSRF layer: the browser sets `Origin` on every state-changing request\n * and a page on another site cannot forge it.\n *\n * Applied to the whole API — including sign-in, which has no session yet and\n * so cannot use the token check below.\n */\nexport function originGuard(): MiddlewareHandler<Env> {\n return async (c, next) => {\n if (SAFE_METHODS.has(c.req.method)) {\n await next();\n return;\n }\n\n const { config } = c.get(\"ctx\");\n const origin = c.req.header(\"origin\");\n\n if (origin) {\n if (!allowedOrigins(config).includes(normaliseOrigin(origin))) {\n throw new ApiError(\"forbidden\", \"Request origin is not allowed.\");\n }\n } else if (config.isProduction) {\n // A browser always sends Origin on writes; its absence means the request\n // did not come from one. Tolerated in development for curl-based testing.\n throw new ApiError(\"forbidden\", \"Missing Origin header.\");\n }\n\n await next();\n };\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Middleware.\n *\n * Ordering matters and is asserted in `app.ts`:\n * requestContext → guards → securityHeaders → errorBoundary → rateLimit\n * → originGuard → the auth surface's gate → route\n *\n * The gate itself is not here. Whether a request is anybody — a session\n * middleware, `allowAll()` — belongs to the composing app, which is what\n * keeps this package authentication-neutral.\n */\n\nimport { ContentPathError } from \"@micacms/core\";\nimport type { ErrorHandler, MiddlewareHandler } from \"hono\";\nimport { bodyLimit } from \"hono/body-limit\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\n\nimport { ApiError, badRequest } from \"../lib/errors.js\";\nimport { clientIp, type Env } from \"../http.js\";\nimport { randomToken } from \"../lib/id.js\";\n\n/** Attaches the container, a request id and a scoped logger; logs the result. */\nexport function requestContext(ctx: Env[\"Variables\"][\"ctx\"]): MiddlewareHandler<Env> {\n return async (c, next) => {\n // A client's id is logged and echoed, so only a short, plain one is kept (#150).\n const offered = c.req.header(\"x-request-id\");\n const requestId = offered && /^[A-Za-z0-9_-]{1,64}$/.test(offered) ? offered : randomToken(8);\n const logger = ctx.logger.child({ requestId });\n\n c.set(\"ctx\", ctx);\n c.set(\"requestId\", requestId);\n c.set(\"logger\", logger);\n c.header(\"x-request-id\", requestId);\n\n const startedAt = performance.now();\n await next();\n const duration = Math.round(performance.now() - startedAt);\n\n const line = {\n method: c.req.method,\n path: new URL(c.req.url).pathname,\n status: c.res.status,\n duration,\n };\n\n /**\n * \"Am I signed in?\" — \"No.\"\n *\n * `/auth/me` is the SPA's first request on every page load, and a 401 is\n * one of its two correct answers, not a rejection worth a warning. Logging\n * it as one puts a scary line in the terminal of everybody who has simply\n * opened the editor while signed out.\n */\n const expected401 = c.res.status === 401 && line.path === \"/api/auth/me\";\n\n if (c.res.status >= 500) logger.error(\"Request failed\", line);\n else if (c.res.status >= 400 && !expected401) logger.warn(\"Request rejected\", line);\n else logger.debug(\"Request\", line);\n };\n}\n\n/**\n * Same-origin only. `blob:` and `data:` images cover previews; a route that\n * needs a stricter policy, such as the sandbox on media files, sets its own.\n */\nexport const CONTENT_SECURITY_POLICY = [\n \"default-src 'self'\",\n \"img-src 'self' blob: data:\",\n \"object-src 'none'\",\n \"base-uri 'none'\",\n \"frame-ancestors 'none'\",\n].join(\"; \");\n\nexport function securityHeaders(): MiddlewareHandler<Env> {\n return async (c, next) => {\n await next();\n\n if (!c.res.headers.has(\"content-security-policy\")) {\n c.header(\"content-security-policy\", CONTENT_SECURITY_POLICY);\n }\n // Only for an https public URL: HSTS over plain HTTP is ignored, and on\n // localhost it would pin a developer's other projects to https.\n if (c.get(\"ctx\").config.MICA_PUBLIC_URL.startsWith(\"https://\")) {\n c.header(\"strict-transport-security\", \"max-age=31536000\");\n }\n\n c.header(\"x-content-type-options\", \"nosniff\");\n c.header(\"referrer-policy\", \"same-origin\");\n c.header(\"x-frame-options\", \"DENY\");\n // The admin is a first-party SPA; nothing here needs to be embedded.\n c.header(\"permissions-policy\", \"geolocation=(), microphone=(), camera=()\");\n };\n}\n\n/** Files in one upload request; the Media view sends a selection in one request. */\nexport const MAX_FILES_PER_UPLOAD = 10;\n\n/** Far more than any entry's JSON, and small enough to refuse a flood early. */\nexport const MAX_JSON_BYTES = 2 * 1024 * 1024;\n\n/**\n * Refuses an oversized body before a handler reads it into memory (#148). An\n * upload may carry `MAX_FILES_PER_UPLOAD` files of `MICA_MAX_UPLOAD_BYTES`, plus\n * room for the multipart framing; every other request gets `MAX_JSON_BYTES`.\n */\nexport function requestSizeLimit(): MiddlewareHandler<Env> {\n return async (c, next) => {\n const { config } = c.get(\"ctx\");\n const upload = c.req.method === \"POST\" && c.req.path === \"/api/admin/media\";\n const maxSize = upload\n ? config.MICA_MAX_UPLOAD_BYTES * MAX_FILES_PER_UPLOAD + 1024 * 1024\n : MAX_JSON_BYTES;\n\n return bodyLimit({\n maxSize,\n onError: (ctx) => {\n const refused = new ApiError(\n \"payload_too_large\",\n upload\n ? \"The upload is too large. Upload fewer or smaller files at a time.\"\n : \"The request is too large.\",\n );\n return ctx.json(refused.toBody(), 413);\n },\n })(c, next);\n };\n}\n\n/**\n * Converts thrown errors into the API error envelope. Deliberate failures\n * (`ApiError`) are reported as-is; anything else is a bug and becomes a bare\n * 500, with the stack kept in the log rather than sent to the client.\n *\n * Registered with `app.onError`, not as middleware: Hono's `compose` wraps each\n * handler individually and routes a throw to `onError` at that level, so a\n * try/catch in an outer middleware would never see it.\n */\nexport function errorHandler(): ErrorHandler<Env> {\n return (error, c) => {\n const logger = c.get(\"logger\") ?? c.get(\"ctx\").logger;\n\n if (error instanceof ApiError) {\n logger.debug(\"Handled error\", { code: error.code, message: error.message });\n return c.json(error.toBody(), error.status as ContentfulStatusCode, error.headers);\n }\n\n // A path that could leave the content root is the client's mistake, not a bug.\n if (error instanceof ContentPathError) {\n logger.warn(\"Refused content path\", { message: error.message });\n const refused = badRequest(error.message);\n return c.json(refused.toBody(), refused.status as ContentfulStatusCode);\n }\n\n logger.error(\"Unhandled error\", {\n error: error instanceof Error ? (error.stack ?? error.message) : String(error),\n path: new URL(c.req.url).pathname,\n });\n\n return c.json(\n {\n error: {\n code: \"internal_error\" as const,\n message: \"Something went wrong. Please try again.\",\n requestId: c.get(\"requestId\"),\n },\n },\n 500,\n );\n };\n}\n\n/** Coarse per-IP limit, applied to the whole API surface. */\nexport function rateLimit(): MiddlewareHandler<Env> {\n return async (c, next) => {\n const { limiters } = c.get(\"ctx\");\n const result = limiters.api.check(clientIp(c));\n\n if (!result.allowed) {\n throw new ApiError(\"rate_limited\", \"Too many requests. Please slow down.\", {\n headers: { \"retry-after\": String(result.retryAfter) },\n });\n }\n\n await next();\n };\n}\n\nconst SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\nconst normaliseOrigin = (value: string): string => {\n try {\n return new URL(value).origin;\n } catch {\n return value;\n }\n};\n\nexport function allowedOrigins(config: {\n MICA_PUBLIC_URL: string;\n adminOrigins: string[];\n}): string[] {\n return [...new Set([config.MICA_PUBLIC_URL, ...config.adminOrigins].map(normaliseOrigin))];\n}\n\n/**\n * First CSRF layer: the browser sets `Origin` on every state-changing request\n * and a page on another site cannot forge it.\n *\n * Applied to the whole API — including sign-in, which has no session yet and\n * so cannot use the token check below.\n */\nexport function originGuard(): MiddlewareHandler<Env> {\n return async (c, next) => {\n if (SAFE_METHODS.has(c.req.method)) {\n await next();\n return;\n }\n\n const { config } = c.get(\"ctx\");\n const origin = c.req.header(\"origin\");\n\n if (origin) {\n if (!allowedOrigins(config).includes(normaliseOrigin(origin))) {\n throw new ApiError(\"forbidden\", \"Request origin is not allowed.\");\n }\n } else if (config.isProduction) {\n // A browser always sends Origin on writes; its absence means the request\n // did not come from one. Tolerated in development for curl-based testing.\n throw new ApiError(\"forbidden\", \"Missing Origin header.\");\n }\n\n await next();\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"admin-content.d.ts","sourceRoot":"","sources":["../../src/routes/admin-content.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAiCtC,wBAAgB,kBAAkB,IAAI,IAAI,CAAC,GAAG,CAAC,CAoJ9C"}
1
+ {"version":3,"file":"admin-content.d.ts","sourceRoot":"","sources":["../../src/routes/admin-content.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAmCtC,wBAAgB,kBAAkB,IAAI,IAAI,CAAC,GAAG,CAAC,CAoJ9C"}
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { ReadOnlyError, ValidationError } from "../services/content-store.js";
7
7
  import { ConflictError } from "../workspace/files.js";
8
- import { collectionEntryPath, compareEntries, entryTitle, singletonPath } from "@micacms/core";
8
+ import { collectionEntryPath, compareEntries, entryTitle, LayoutError, singletonPath } from "@micacms/core";
9
9
  import { Hono } from "hono";
10
10
  import { z } from "zod";
11
11
  import { ApiError, badRequest, notFound } from "../lib/errors.js";
@@ -30,8 +30,9 @@ async function handled(run) {
30
30
  return await run();
31
31
  }
32
32
  catch (error) {
33
- if (error instanceof ConflictError || error instanceof ReadOnlyError)
33
+ if (error instanceof ConflictError || error instanceof ReadOnlyError || error instanceof LayoutError) {
34
34
  throw new ApiError("conflict", error.message);
35
+ }
35
36
  if (error instanceof ValidationError) {
36
37
  throw new ApiError("validation_failed", error.message, { issues: error.issues });
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"admin-content.js","sourceRoot":"","sources":["../../src/routes/admin-content.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin routes for entries and singletons. The content store holds every rule;\n * these routes only parse and serialize. The API path is the repository path,\n * Git is the history, and `revision` makes concurrent edits safe.\n */\n\nimport { ReadOnlyError, ValidationError } from \"../services/content-store.js\";\nimport { ConflictError } from \"../workspace/files.js\";\nimport { collectionEntryPath, compareEntries, entryTitle, singletonPath } from \"@micacms/core\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\n\nimport { ApiError, badRequest, notFound } from \"../lib/errors.js\";\nimport type { Env } from \"../http.js\";\n\nconst saveSchema = z.object({\n data: z.record(z.string(), z.unknown()),\n slug: z.string().max(120).optional(),\n /**\n * The revision the editor opened. `null` asserts that the entry does not\n * exist, so two parallel creates cannot overwrite.\n */\n revision: z.string().nullable().optional(),\n});\n\nconst reorderSchema = z.object({ slugs: z.array(z.string().min(1)).max(2000) });\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/** Maps a conflict to 409 and a validation failure to 422; other errors go to the error handler. */\nasync function handled<T>(run: () => Promise<T>): Promise<T> {\n try {\n return await run();\n } catch (error) {\n if (error instanceof ConflictError || error instanceof ReadOnlyError) throw new ApiError(\"conflict\", error.message);\n if (error instanceof ValidationError) {\n throw new ApiError(\"validation_failed\", error.message, { issues: error.issues });\n }\n throw error;\n }\n}\n\nexport function adminContentRoutes(): Hono<Env> {\n const app = new Hono<Env>();\n\n app.get(\"/collections/:collection/entries\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const collection = c.req.param(\"collection\");\n const def = content.requireCollection(collection);\n\n const entries = await handled(() => content.list(collection));\n const search = c.req.query(\"search\")?.trim().toLowerCase();\n\n const items = entries\n .map((entry) => ({\n slug: entry.slug,\n position: entry.position,\n title: entryTitle(def, entry.data),\n data: entry.data,\n }))\n // Content trees are small, so an in-memory substring match is sufficient.\n .filter((item) => !search || JSON.stringify(item.data).toLowerCase().includes(search))\n .sort(compareEntries(def));\n\n return c.json({ items, total: items.length });\n });\n\n app.post(\"/collections/:collection/entries\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const parsed = saveSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"The entry payload is malformed.\");\n\n const saved = await handled(() =>\n content.save(c.req.param(\"collection\"), {\n data: parsed.data.data,\n expectedRevision: parsed.data.revision ?? null,\n }),\n );\n\n return c.json(saved, 201);\n });\n\n app.post(\"/collections/:collection/reorder\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const parsed = reorderSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"Expected a list of entry slugs.\");\n\n await handled(() => content.reorder(c.req.param(\"collection\"), parsed.data.slugs));\n return c.json({ ok: true });\n });\n\n /** Server-side slug generation, so the URL preview matches what is saved. */\n app.get(\"/collections/:collection/slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const source = c.req.query(\"source\") ?? \"\";\n if (!source.trim()) return c.json({ slug: \"\" });\n\n return c.json({\n slug: await handled(() =>\n content.suggestSlug(c.req.param(\"collection\"), source, c.req.query(\"except\")),\n ),\n });\n });\n\n app.get(\"/collections/:collection/entries/:slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const collection = c.req.param(\"collection\");\n const slug = c.req.param(\"slug\");\n\n const entry = await handled(() => content.get(collection, slug));\n if (!entry) throw notFound(\"This entry no longer exists.\");\n\n const def = content.requireCollection(collection);\n\n return c.json({\n collection,\n slug: entry.slug,\n position: entry.position,\n title: entryTitle(def, entry.data),\n data: entry.data,\n // From the model, so the editor need not know when an entry is a directory.\n path: collectionEntryPath(def.fields, collection, entry.slug),\n revision: await content.revisionOf(collection, slug),\n });\n });\n\n app.put(\"/collections/:collection/entries/:slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const parsed = saveSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"The entry payload is malformed.\");\n\n const saved = await handled(() =>\n content.save(c.req.param(\"collection\"), {\n data: parsed.data.data,\n slug: c.req.param(\"slug\"),\n expectedRevision: parsed.data.revision,\n }),\n );\n\n return c.json(saved);\n });\n\n app.delete(\"/collections/:collection/entries/:slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n\n await handled(() =>\n content.remove(c.req.param(\"collection\"), c.req.param(\"slug\")),\n );\n\n return c.json({ ok: true });\n });\n\n /** Entries elsewhere that point at this one — shown before a delete. */\n app.get(\"/collections/:collection/entries/:slug/references\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const references = await handled(() =>\n content.findReferences(c.req.param(\"collection\"), c.req.param(\"slug\")),\n );\n\n return c.json({ references });\n });\n\n app.get(\"/singletons/:name\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const name = c.req.param(\"name\");\n const def = content.requireSingleton(name);\n\n return c.json({\n name,\n label: def.label,\n path: singletonPath(def.fields, name),\n data: await handled(() => content.getSingleton(name)),\n revision: await content.revisionOfSingleton(name),\n });\n });\n\n app.put(\"/singletons/:name\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const body = (await jsonBody(c)) as { data?: unknown; revision?: string | null };\n\n const saved = await handled(() =>\n content.saveSingleton(c.req.param(\"name\"), body?.data ?? {}, {\n expectedRevision: body?.revision,\n }),\n );\n\n return c.json(saved);\n });\n\n return app;\n}\n"]}
1
+ {"version":3,"file":"admin-content.js","sourceRoot":"","sources":["../../src/routes/admin-content.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin routes for entries and singletons. The content store holds every rule;\n * these routes only parse and serialize. The API path is the repository path,\n * Git is the history, and `revision` makes concurrent edits safe.\n */\n\nimport { ReadOnlyError, ValidationError } from \"../services/content-store.js\";\nimport { ConflictError } from \"../workspace/files.js\";\nimport { collectionEntryPath, compareEntries, entryTitle, LayoutError, singletonPath } from \"@micacms/core\";\nimport { Hono } from \"hono\";\nimport { z } from \"zod\";\n\nimport { ApiError, badRequest, notFound } from \"../lib/errors.js\";\nimport type { Env } from \"../http.js\";\n\nconst saveSchema = z.object({\n data: z.record(z.string(), z.unknown()),\n slug: z.string().max(120).optional(),\n /**\n * The revision the editor opened. `null` asserts that the entry does not\n * exist, so two parallel creates cannot overwrite.\n */\n revision: z.string().nullable().optional(),\n});\n\nconst reorderSchema = z.object({ slugs: z.array(z.string().min(1)).max(2000) });\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/** Maps a conflict to 409 and a validation failure to 422; other errors go to the error handler. */\nasync function handled<T>(run: () => Promise<T>): Promise<T> {\n try {\n return await run();\n } catch (error) {\n if (error instanceof ConflictError || error instanceof ReadOnlyError || error instanceof LayoutError) {\n throw new ApiError(\"conflict\", error.message);\n }\n if (error instanceof ValidationError) {\n throw new ApiError(\"validation_failed\", error.message, { issues: error.issues });\n }\n throw error;\n }\n}\n\nexport function adminContentRoutes(): Hono<Env> {\n const app = new Hono<Env>();\n\n app.get(\"/collections/:collection/entries\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const collection = c.req.param(\"collection\");\n const def = content.requireCollection(collection);\n\n const entries = await handled(() => content.list(collection));\n const search = c.req.query(\"search\")?.trim().toLowerCase();\n\n const items = entries\n .map((entry) => ({\n slug: entry.slug,\n position: entry.position,\n title: entryTitle(def, entry.data),\n data: entry.data,\n }))\n // Content trees are small, so an in-memory substring match is sufficient.\n .filter((item) => !search || JSON.stringify(item.data).toLowerCase().includes(search))\n .sort(compareEntries(def));\n\n return c.json({ items, total: items.length });\n });\n\n app.post(\"/collections/:collection/entries\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const parsed = saveSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"The entry payload is malformed.\");\n\n const saved = await handled(() =>\n content.save(c.req.param(\"collection\"), {\n data: parsed.data.data,\n expectedRevision: parsed.data.revision ?? null,\n }),\n );\n\n return c.json(saved, 201);\n });\n\n app.post(\"/collections/:collection/reorder\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const parsed = reorderSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"Expected a list of entry slugs.\");\n\n await handled(() => content.reorder(c.req.param(\"collection\"), parsed.data.slugs));\n return c.json({ ok: true });\n });\n\n /** Server-side slug generation, so the URL preview matches what is saved. */\n app.get(\"/collections/:collection/slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const source = c.req.query(\"source\") ?? \"\";\n if (!source.trim()) return c.json({ slug: \"\" });\n\n return c.json({\n slug: await handled(() =>\n content.suggestSlug(c.req.param(\"collection\"), source, c.req.query(\"except\")),\n ),\n });\n });\n\n app.get(\"/collections/:collection/entries/:slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const collection = c.req.param(\"collection\");\n const slug = c.req.param(\"slug\");\n\n const entry = await handled(() => content.get(collection, slug));\n if (!entry) throw notFound(\"This entry no longer exists.\");\n\n const def = content.requireCollection(collection);\n\n return c.json({\n collection,\n slug: entry.slug,\n position: entry.position,\n title: entryTitle(def, entry.data),\n data: entry.data,\n // From the model, so the editor need not know when an entry is a directory.\n path: collectionEntryPath(def.fields, collection, entry.slug),\n revision: await content.revisionOf(collection, slug),\n });\n });\n\n app.put(\"/collections/:collection/entries/:slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const parsed = saveSchema.safeParse(await jsonBody(c));\n if (!parsed.success) throw badRequest(\"The entry payload is malformed.\");\n\n const saved = await handled(() =>\n content.save(c.req.param(\"collection\"), {\n data: parsed.data.data,\n slug: c.req.param(\"slug\"),\n expectedRevision: parsed.data.revision,\n }),\n );\n\n return c.json(saved);\n });\n\n app.delete(\"/collections/:collection/entries/:slug\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n\n await handled(() =>\n content.remove(c.req.param(\"collection\"), c.req.param(\"slug\")),\n );\n\n return c.json({ ok: true });\n });\n\n /** Entries elsewhere that point at this one — shown before a delete. */\n app.get(\"/collections/:collection/entries/:slug/references\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const references = await handled(() =>\n content.findReferences(c.req.param(\"collection\"), c.req.param(\"slug\")),\n );\n\n return c.json({ references });\n });\n\n app.get(\"/singletons/:name\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const name = c.req.param(\"name\");\n const def = content.requireSingleton(name);\n\n return c.json({\n name,\n label: def.label,\n path: singletonPath(def.fields, name),\n data: await handled(() => content.getSingleton(name)),\n revision: await content.revisionOfSingleton(name),\n });\n });\n\n app.put(\"/singletons/:name\", async (c) => {\n const { content } = c.get(\"ctx\").services;\n const body = (await jsonBody(c)) as { data?: unknown; revision?: string | null };\n\n const saved = await handled(() =>\n content.saveSingleton(c.req.param(\"name\"), body?.data ?? {}, {\n expectedRevision: body?.revision,\n }),\n );\n\n return c.json(saved);\n });\n\n return app;\n}\n"]}
@@ -101,14 +101,14 @@ export function adminSystemRoutes() {
101
101
  // A clone that Mica keeps names its remote in `repository.url` instead.
102
102
  contentRoot: repository.owner === "developer" ? config.contentRoot : null,
103
103
  dataDir: operational.dataDir ?? null,
104
- logLevel: config.LOG_LEVEL,
105
- maxUploadBytes: config.MAX_UPLOAD_BYTES,
106
- maxUploadReadable: formatBytes(config.MAX_UPLOAD_BYTES),
104
+ logLevel: config.MICA_LOG_LEVEL,
105
+ maxUploadBytes: config.MICA_MAX_UPLOAD_BYTES,
106
+ maxUploadReadable: formatBytes(config.MICA_MAX_UPLOAD_BYTES),
107
107
  sessionTtlHours: operational.sessionTtlHours ?? null,
108
108
  sessionIdleHours: operational.sessionIdleHours ?? null,
109
109
  auth: operational.auth,
110
110
  admission: operational.admission ?? null,
111
- trustProxy: config.TRUST_PROXY,
111
+ trustProxy: config.MICA_TRUST_PROXY,
112
112
  adminOrigins: config.adminOrigins,
113
113
  generatedSecrets: operational.generatedSecrets ?? [],
114
114
  memoryBytes: process.memoryUsage().rss,
@@ -1 +1 @@
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 readOnly: site.readOnly.get(`collections.${def.name}`) ?? null,\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 readOnly: site.readOnly.get(`singletons.${def.name}`) ?? null,\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"]}
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 readOnly: site.readOnly.get(`collections.${def.name}`) ?? null,\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 readOnly: site.readOnly.get(`singletons.${def.name}`) ?? null,\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.MICA_LOG_LEVEL,\n maxUploadBytes: config.MICA_MAX_UPLOAD_BYTES,\n maxUploadReadable: formatBytes(config.MICA_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.MICA_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/schema.d.ts CHANGED
@@ -14,6 +14,12 @@ export declare function schemaOrigin(source: ContentSource): string;
14
14
  * message because a first deployment hits it.
15
15
  */
16
16
  export declare function loadSchema(source: ContentSource): Promise<Site>;
17
+ /**
18
+ * One message for each collection or singleton with entries in the other
19
+ * layout. Mica refuses to start on any: editing next to hidden entries would
20
+ * let a save create a second copy of a page.
21
+ */
22
+ export declare function layoutProblems(source: ContentSource, site: Site): Promise<string[]>;
17
23
  /**
18
24
  * Runs the `defineSite` checks on untrusted input. A newer `protocolVersion` is
19
25
  * refused; an unknown field kind is not, and `readOnlyParts` names what it locks.
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAUL,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;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,SAAe,GAAG,IAAI,CAuDrE;AAmBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAsB7D;AAUD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnE"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAaL,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;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWzF;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,SAAe,GAAG,IAAI,CAuDrE;AAmBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAsB7D;AAUD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnE"}
package/dist/schema.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * The Astro project writes it. The manifest is untrusted input (another core
4
4
  * version, hand-edited or truncated), so it is validated again here.
5
5
  */
6
- import { checkCompatibility, defineSite, deriveLayout, recordLayout, isFieldKind, isProtocolManifest, MANIFEST_PATH, PROTOCOL_VERSION, } from "@micacms/core";
6
+ import { checkCompatibility, defineSite, deriveLayout, entryLayout, LayoutError, misplacedFiles, recordLayout, isFieldKind, isProtocolManifest, MANIFEST_PATH, PROTOCOL_VERSION, } from "@micacms/core";
7
7
  export class SchemaError extends Error {
8
8
  name = "SchemaError";
9
9
  }
@@ -39,6 +39,24 @@ export async function loadSchema(source) {
39
39
  }
40
40
  return checkSchema(raw, origin);
41
41
  }
42
+ /**
43
+ * One message for each collection or singleton with entries in the other
44
+ * layout. Mica refuses to start on any: editing next to hidden entries would
45
+ * let a save create a second copy of a page.
46
+ */
47
+ export async function layoutProblems(source, site) {
48
+ const problems = [];
49
+ const parts = [
50
+ ...Object.entries(site.collections).map(([name, def]) => [name, def.fields, "collection"]),
51
+ ...Object.entries(site.singletons).map(([name, def]) => [name, def.fields, "singleton"]),
52
+ ];
53
+ for (const [name, fields, kind] of parts) {
54
+ const paths = await misplacedFiles(source, fields, name, kind);
55
+ if (paths.length > 0)
56
+ problems.push(new LayoutError(paths, name, entryLayout(fields)).message);
57
+ }
58
+ return problems;
59
+ }
42
60
  function describeMissing(origin) {
43
61
  return (`No content model at ${origin}.\n` +
44
62
  ` Mica ships without one — the Astro project defines it and writes it here.\n` +
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reads the content model from `.mica/schema.json` in the content repository.\n * The Astro project writes it. The manifest is untrusted input (another core\n * version, hand-edited or truncated), so it is validated again here.\n */\n\nimport {\n checkCompatibility,\n defineSite,\n deriveLayout,\n recordLayout,\n isFieldKind,\n isProtocolManifest,\n MANIFEST_PATH,\n PROTOCOL_VERSION,\n type Collection,\n type ContentSource,\n type FieldMap,\n type Singleton,\n type Site,\n} from \"@micacms/core\";\n\nexport class SchemaError extends Error {\n override name = \"SchemaError\";\n}\n\n/** Where a model came from, for logs and the system screen. */\nexport function schemaOrigin(source: ContentSource): string {\n return `${source.origin}/${MANIFEST_PATH}`;\n}\n\n/**\n * Reads the model from the content repository. A missing file gets a long\n * message because a first deployment hits it.\n */\nexport async function loadSchema(source: ContentSource): Promise<Site> {\n const origin = schemaOrigin(source);\n\n let text: string | null;\n try {\n text = await source.read(MANIFEST_PATH);\n } catch (error) {\n // An unreachable repository is not a bad model; the fixes are different.\n throw new SchemaError(\n `Could not read the content model from ${source.origin}.\\n` +\n ` ${(error as Error).message}\\n` +\n ` The repository has to be reachable before Mica can start: the model\\n` +\n ` lives in it, at ${MANIFEST_PATH}.`,\n );\n }\n\n if (text === null) throw new SchemaError(describeMissing(origin));\n\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error) {\n throw new SchemaError(`${origin} is not valid JSON: ${(error as Error).message}`);\n }\n\n return checkSchema(raw, origin);\n}\n\nfunction describeMissing(origin: string): string {\n return (\n `No content model at ${origin}.\\n` +\n ` Mica ships without one — the Astro project defines it and writes it here.\\n` +\n ` In the project: npx astro build (or start the dev server once)\\n` +\n ` then commit ${MANIFEST_PATH} and push it to the branch Mica is reading.`\n );\n}\n\n/**\n * Runs the `defineSite` checks on untrusted input. A newer `protocolVersion` is\n * refused; an unknown field kind is not, and `readOnlyParts` names what it locks.\n */\nexport function checkSchema(raw: unknown, origin = \"the schema\"): Site {\n if (raw === null || typeof raw !== \"object\") {\n throw new SchemaError(`${origin} is ${raw === null ? \"null\" : typeof raw}, expected an object.`);\n }\n\n // A bare `{ collections }` without the protocol envelope is also accepted.\n if (isProtocolManifest(raw)) {\n const compatibility = checkCompatibility(raw);\n if (!compatibility.ok && compatibility.reason === \"newer\") {\n throw new SchemaError(\n `${origin} follows protocol version ${compatibility.version}; this build of Mica ` +\n `understands version ${PROTOCOL_VERSION}.\\n` +\n ` The project was built against a newer @micacms/astro. Upgrade Mica.`,\n );\n }\n }\n\n const candidate = raw as { collections?: unknown; singletons?: unknown };\n\n if (candidate.collections === null || typeof candidate.collections !== \"object\") {\n throw new SchemaError(`${origin} has no \\`collections\\`. Expected defineSite({ collections: { ... } }).`);\n }\n\n const rawSingletons = candidate.singletons;\n if (rawSingletons !== undefined && (rawSingletons === null || typeof rawSingletons !== \"object\")) {\n throw new SchemaError(`${origin} has a \\`singletons\\` that is not an object.`);\n }\n\n const collections = candidate.collections as Record<string, Collection>;\n const singletons = (rawSingletons ?? {}) as Record<string, Singleton>;\n\n for (const [name, def] of Object.entries(collections)) {\n assertFieldMap(def?.fields, `collections.${name}`, origin);\n }\n for (const [name, def] of Object.entries(singletons)) {\n assertFieldMap(def?.fields, `singletons.${name}`, origin);\n }\n\n // Drop, do not refuse, a default sort on an undeclared field: only the list order is affected.\n for (const def of Object.values(collections)) {\n const field = def?.defaultSort?.field;\n if (field && field !== \"position\" && !def.fields?.[field]) delete def.defaultSort;\n }\n\n try {\n const site = defineSite({ collections, singletons });\n // Read and classify entries where the manifest says they are, not where fields this build may not know imply.\n for (const def of [...Object.values(site.collections), ...Object.values(site.singletons)]) {\n const stored = (def as { layout?: unknown }).layout;\n if (stored === \"file\" || stored === \"directory\") recordLayout(def.fields, stored);\n }\n return site;\n } catch (error) {\n throw new SchemaError(`${origin} is not a valid content model.\\n ${(error as Error).message}`);\n }\n}\n\nfunction assertFieldMap(fields: unknown, at: string, origin: string): void {\n if (fields === null || typeof fields !== \"object\") {\n throw new SchemaError(`${origin}: \"${at}\" has no \\`fields\\` object.`);\n }\n\n for (const [key, field] of Object.entries(fields as FieldMap)) {\n const here = `${at}.${key}`;\n\n if (field === null || typeof field !== \"object\") {\n throw new SchemaError(`${origin}: field \"${here}\" is not an object.`);\n }\n if (field.kind === \"object\" || field.kind === \"repeater\") {\n assertFieldMap(field.fields, here, origin);\n }\n }\n}\n\n/**\n * The collections and singletons this build of Mica must not write, keyed\n * `collections.<name>` or `singletons.<name>`, with the reason for the editor.\n * A field kind from a newer @micacms/astro makes only its own collection or\n * singleton read-only; Mica still starts, and the rest stays editable.\n */\nexport function readOnlyParts(site: Site): Map<string, string> {\n const parts = new Map<string, string>();\n const add = (key: string, def: Collection | Singleton) => {\n const unknown = unknownKinds(def.fields, []);\n const stored = (def as { layout?: unknown }).layout;\n if (unknown.length > 0) {\n parts.set(\n key,\n `\"${def.label}\" uses ${unknown.map((field) => `\"${field.path}\" (${field.kind})`).join(\", \")}, ` +\n \"a field type that this version of Mica does not know. Upgrade Mica to edit it.\",\n );\n } else if (stored !== undefined && stored !== deriveLayout(def.fields)) {\n parts.set(\n key,\n `\"${def.label}\" is stored as ${String(stored)}, which this version of Mica does not expect. ` +\n \"Upgrade Mica to edit it.\",\n );\n }\n };\n for (const [name, def] of Object.entries(site.collections)) add(`collections.${name}`, def);\n for (const [name, def] of Object.entries(site.singletons)) add(`singletons.${name}`, def);\n return parts;\n}\n\nfunction unknownKinds(fields: FieldMap, at: string[]): Array<{ path: string; kind: string }> {\n return Object.entries(fields).flatMap(([key, field]) => {\n const here = [...at, key];\n if (!isFieldKind(field.kind)) return [{ path: here.join(\".\"), kind: String(field.kind) }];\n return field.kind === \"object\" || field.kind === \"repeater\" ? unknownKinds(field.fields, here) : [];\n });\n}\n\n/**\n * A short digest of a model, to show drift. Field order counts, because it is\n * the order of the admin form.\n */\nexport async function schemaFingerprint(site: Site): Promise<string> {\n const { createHash } = await import(\"node:crypto\");\n return createHash(\"sha256\").update(JSON.stringify(site)).digest(\"hex\").slice(0, 12);\n}\n"]}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reads the content model from `.mica/schema.json` in the content repository.\n * The Astro project writes it. The manifest is untrusted input (another core\n * version, hand-edited or truncated), so it is validated again here.\n */\n\nimport {\n checkCompatibility,\n defineSite,\n deriveLayout,\n entryLayout,\n LayoutError,\n misplacedFiles,\n recordLayout,\n isFieldKind,\n isProtocolManifest,\n MANIFEST_PATH,\n PROTOCOL_VERSION,\n type Collection,\n type ContentSource,\n type FieldMap,\n type Singleton,\n type Site,\n} from \"@micacms/core\";\n\nexport class SchemaError extends Error {\n override name = \"SchemaError\";\n}\n\n/** Where a model came from, for logs and the system screen. */\nexport function schemaOrigin(source: ContentSource): string {\n return `${source.origin}/${MANIFEST_PATH}`;\n}\n\n/**\n * Reads the model from the content repository. A missing file gets a long\n * message because a first deployment hits it.\n */\nexport async function loadSchema(source: ContentSource): Promise<Site> {\n const origin = schemaOrigin(source);\n\n let text: string | null;\n try {\n text = await source.read(MANIFEST_PATH);\n } catch (error) {\n // An unreachable repository is not a bad model; the fixes are different.\n throw new SchemaError(\n `Could not read the content model from ${source.origin}.\\n` +\n ` ${(error as Error).message}\\n` +\n ` The repository has to be reachable before Mica can start: the model\\n` +\n ` lives in it, at ${MANIFEST_PATH}.`,\n );\n }\n\n if (text === null) throw new SchemaError(describeMissing(origin));\n\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error) {\n throw new SchemaError(`${origin} is not valid JSON: ${(error as Error).message}`);\n }\n\n return checkSchema(raw, origin);\n}\n\n/**\n * One message for each collection or singleton with entries in the other\n * layout. Mica refuses to start on any: editing next to hidden entries would\n * let a save create a second copy of a page.\n */\nexport async function layoutProblems(source: ContentSource, site: Site): Promise<string[]> {\n const problems: string[] = [];\n const parts = [\n ...Object.entries(site.collections).map(([name, def]) => [name, def.fields, \"collection\"] as const),\n ...Object.entries(site.singletons).map(([name, def]) => [name, def.fields, \"singleton\"] as const),\n ];\n for (const [name, fields, kind] of parts) {\n const paths = await misplacedFiles(source, fields, name, kind);\n if (paths.length > 0) problems.push(new LayoutError(paths, name, entryLayout(fields)).message);\n }\n return problems;\n}\n\nfunction describeMissing(origin: string): string {\n return (\n `No content model at ${origin}.\\n` +\n ` Mica ships without one — the Astro project defines it and writes it here.\\n` +\n ` In the project: npx astro build (or start the dev server once)\\n` +\n ` then commit ${MANIFEST_PATH} and push it to the branch Mica is reading.`\n );\n}\n\n/**\n * Runs the `defineSite` checks on untrusted input. A newer `protocolVersion` is\n * refused; an unknown field kind is not, and `readOnlyParts` names what it locks.\n */\nexport function checkSchema(raw: unknown, origin = \"the schema\"): Site {\n if (raw === null || typeof raw !== \"object\") {\n throw new SchemaError(`${origin} is ${raw === null ? \"null\" : typeof raw}, expected an object.`);\n }\n\n // A bare `{ collections }` without the protocol envelope is also accepted.\n if (isProtocolManifest(raw)) {\n const compatibility = checkCompatibility(raw);\n if (!compatibility.ok && compatibility.reason === \"newer\") {\n throw new SchemaError(\n `${origin} follows protocol version ${compatibility.version}; this build of Mica ` +\n `understands version ${PROTOCOL_VERSION}.\\n` +\n ` The project was built against a newer @micacms/astro. Upgrade Mica.`,\n );\n }\n }\n\n const candidate = raw as { collections?: unknown; singletons?: unknown };\n\n if (candidate.collections === null || typeof candidate.collections !== \"object\") {\n throw new SchemaError(`${origin} has no \\`collections\\`. Expected defineSite({ collections: { ... } }).`);\n }\n\n const rawSingletons = candidate.singletons;\n if (rawSingletons !== undefined && (rawSingletons === null || typeof rawSingletons !== \"object\")) {\n throw new SchemaError(`${origin} has a \\`singletons\\` that is not an object.`);\n }\n\n const collections = candidate.collections as Record<string, Collection>;\n const singletons = (rawSingletons ?? {}) as Record<string, Singleton>;\n\n for (const [name, def] of Object.entries(collections)) {\n assertFieldMap(def?.fields, `collections.${name}`, origin);\n }\n for (const [name, def] of Object.entries(singletons)) {\n assertFieldMap(def?.fields, `singletons.${name}`, origin);\n }\n\n // Drop, do not refuse, a default sort on an undeclared field: only the list order is affected.\n for (const def of Object.values(collections)) {\n const field = def?.defaultSort?.field;\n if (field && field !== \"position\" && !def.fields?.[field]) delete def.defaultSort;\n }\n\n try {\n const site = defineSite({ collections, singletons });\n // Read and classify entries where the manifest says they are, not where fields this build may not know imply.\n for (const def of [...Object.values(site.collections), ...Object.values(site.singletons)]) {\n const stored = (def as { layout?: unknown }).layout;\n if (stored === \"file\" || stored === \"directory\") recordLayout(def.fields, stored);\n }\n return site;\n } catch (error) {\n throw new SchemaError(`${origin} is not a valid content model.\\n ${(error as Error).message}`);\n }\n}\n\nfunction assertFieldMap(fields: unknown, at: string, origin: string): void {\n if (fields === null || typeof fields !== \"object\") {\n throw new SchemaError(`${origin}: \"${at}\" has no \\`fields\\` object.`);\n }\n\n for (const [key, field] of Object.entries(fields as FieldMap)) {\n const here = `${at}.${key}`;\n\n if (field === null || typeof field !== \"object\") {\n throw new SchemaError(`${origin}: field \"${here}\" is not an object.`);\n }\n if (field.kind === \"object\" || field.kind === \"repeater\") {\n assertFieldMap(field.fields, here, origin);\n }\n }\n}\n\n/**\n * The collections and singletons this build of Mica must not write, keyed\n * `collections.<name>` or `singletons.<name>`, with the reason for the editor.\n * A field kind from a newer @micacms/astro makes only its own collection or\n * singleton read-only; Mica still starts, and the rest stays editable.\n */\nexport function readOnlyParts(site: Site): Map<string, string> {\n const parts = new Map<string, string>();\n const add = (key: string, def: Collection | Singleton) => {\n const unknown = unknownKinds(def.fields, []);\n const stored = (def as { layout?: unknown }).layout;\n if (unknown.length > 0) {\n parts.set(\n key,\n `\"${def.label}\" uses ${unknown.map((field) => `\"${field.path}\" (${field.kind})`).join(\", \")}, ` +\n \"a field type that this version of Mica does not know. Upgrade Mica to edit it.\",\n );\n } else if (stored !== undefined && stored !== deriveLayout(def.fields)) {\n parts.set(\n key,\n `\"${def.label}\" is stored as ${String(stored)}, which this version of Mica does not expect. ` +\n \"Upgrade Mica to edit it.\",\n );\n }\n };\n for (const [name, def] of Object.entries(site.collections)) add(`collections.${name}`, def);\n for (const [name, def] of Object.entries(site.singletons)) add(`singletons.${name}`, def);\n return parts;\n}\n\nfunction unknownKinds(fields: FieldMap, at: string[]): Array<{ path: string; kind: string }> {\n return Object.entries(fields).flatMap(([key, field]) => {\n const here = [...at, key];\n if (!isFieldKind(field.kind)) return [{ path: here.join(\".\"), kind: String(field.kind) }];\n return field.kind === \"object\" || field.kind === \"repeater\" ? unknownKinds(field.fields, here) : [];\n });\n}\n\n/**\n * A short digest of a model, to show drift. Field order counts, because it is\n * the order of the admin form.\n */\nexport async function schemaFingerprint(site: Site): Promise<string> {\n const { createHash } = await import(\"node:crypto\");\n return createHash(\"sha256\").update(JSON.stringify(site)).digest(\"hex\").slice(0, 12);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../../src/services/changes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../../src/services/changes.ts"],"names":[],"mappings":"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"}