@micacms/runtime 0.5.0-rc.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -1
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/routes/admin-content.js +2 -2
- package/dist/routes/admin-content.js.map +1 -1
- package/dist/routes/admin-media.d.ts.map +1 -1
- package/dist/routes/admin-media.js +6 -1
- package/dist/routes/admin-media.js.map +1 -1
- package/dist/routes/admin-system.d.ts.map +1 -1
- package/dist/routes/admin-system.js +2 -0
- package/dist/routes/admin-system.js.map +1 -1
- package/dist/schema.d.ts +9 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +45 -10
- package/dist/schema.js.map +1 -1
- package/dist/services/content-store.d.ts +6 -1
- package/dist/services/content-store.d.ts.map +1 -1
- package/dist/services/content-store.js +16 -5
- package/dist/services/content-store.js.map +1 -1
- package/dist/services/staging.d.ts +2 -0
- package/dist/services/staging.d.ts.map +1 -1
- package/dist/services/staging.js +8 -0
- package/dist/services/staging.js.map +1 -1
- package/package.json +2 -2
package/dist/context.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface SiteContext {
|
|
|
27
27
|
* drift gate compares against it.
|
|
28
28
|
*/
|
|
29
29
|
schemaRevision: string | null;
|
|
30
|
+
/** Collections and singletons this build cannot write, from `readOnlyParts`, with the reason. */
|
|
31
|
+
readOnly: ReadonlyMap<string, string>;
|
|
30
32
|
}
|
|
31
33
|
export declare const DEFAULT_SITE_ID = "default";
|
|
32
34
|
/** Facts about the composition that the System page shows and the runtime cannot know. */
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/context.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { createLogger } from "./lib/logger.js";
|
|
7
7
|
import { createRateLimiter } from "./lib/rate-limit.js";
|
|
8
|
+
import { readOnlyParts } from "./schema.js";
|
|
8
9
|
import { createMediaService } from "./services/media.js";
|
|
9
10
|
import { createContentStore } from "./services/content-store.js";
|
|
10
11
|
import { createWorkingTreeStagingService } from "./services/staging.js";
|
|
@@ -20,14 +21,18 @@ export function createRuntimeContext(options) {
|
|
|
20
21
|
const schemaRevision = options.schemaRevision ?? null;
|
|
21
22
|
// Services write through the lock wrapper; staging takes the locks itself.
|
|
22
23
|
const locks = options.locks ?? createWorkspaceLocks();
|
|
24
|
+
const readOnly = readOnlyParts(model);
|
|
25
|
+
for (const reason of readOnly.values())
|
|
26
|
+
logger.warn("Read-only in this version of Mica", { reason });
|
|
23
27
|
const site = {
|
|
24
28
|
id: options.siteId ?? DEFAULT_SITE_ID,
|
|
25
29
|
model,
|
|
26
30
|
contentRepository: lockedFiles(options.files, locks.tree),
|
|
27
31
|
schemaRevision,
|
|
32
|
+
readOnly,
|
|
28
33
|
};
|
|
29
34
|
const services = {
|
|
30
|
-
content: createContentStore(model, site.contentRepository),
|
|
35
|
+
content: createContentStore(model, site.contentRepository, readOnly),
|
|
31
36
|
media: createMediaService({
|
|
32
37
|
files: site.contentRepository,
|
|
33
38
|
maxBytes: config.MAX_UPLOAD_BYTES,
|
|
@@ -37,6 +42,7 @@ export function createRuntimeContext(options) {
|
|
|
37
42
|
files: options.files,
|
|
38
43
|
contentRoot: config.contentRoot,
|
|
39
44
|
schemaRevision,
|
|
45
|
+
readOnly,
|
|
40
46
|
publishEnabled: config.MICA_LOCAL_PUBLISH,
|
|
41
47
|
logger,
|
|
42
48
|
locks,
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,YAAY,EAAe,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAoB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAqB,MAAM,qBAAqB,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAqB,MAAM,6BAA6B,CAAC;AACpF,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAuB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Builds the runtime's services for one site, with no module-level singletons.\n * The app supplies the working tree's files and the owner policy, so the\n * runtime stays authentication-neutral.\n */\n\nimport type { Site } from \"@micacms/core\";\n\nimport type { RuntimeConfig } from \"./config.js\";\nimport { createLogger, type Logger } from \"./lib/logger.js\";\nimport { createRateLimiter, type RateLimiter } from \"./lib/rate-limit.js\";\nimport { readOnlyParts } from \"./schema.js\";\nimport { createMediaService, type MediaService } from \"./services/media.js\";\nimport type { ContentStagingService } from \"./services/changes.js\";\nimport { createContentStore, type ContentStore } from \"./services/content-store.js\";\nimport { createWorkingTreeStagingService } from \"./services/staging.js\";\nimport { createWorkspaceLocks, lockedFiles, type WorkspaceLocks } from \"./workspace/locks.js\";\nimport type { OwnerPolicy } from \"./workspace/policy.js\";\nimport type { ContentFiles } from \"./workspace/files.js\";\nimport type { GitTree } from \"./workspace/tree.js\";\n\n/** The one site this process serves. Several sites means several processes. */\nexport interface SiteContext {\n /** Identifies this site in logs. */\n id: string;\n /** The content model, read from the repository at startup. */\n model: Site;\n /** The working tree's files behind the workspace locks. Only Publish makes a commit. */\n contentRepository: ContentFiles;\n /**\n * Revision of `.mica/schema.json` when the model was loaded. The publish\n * drift gate compares against it.\n */\n schemaRevision: string | null;\n /** Collections and singletons this build cannot write, from `readOnlyParts`, with the reason. */\n readOnly: ReadonlyMap<string, string>;\n}\n\nexport const DEFAULT_SITE_ID = \"default\";\n\n/** Facts about the composition that the System page shows and the runtime cannot know. */\nexport interface OperationalInfo {\n /** How this deployment authenticates, in words. */\n auth: string;\n /** What decides who may edit, in the operator's own words. */\n admission?: string;\n dataDir?: string;\n sessionTtlHours?: number;\n sessionIdleHours?: number;\n /** Names of the secrets this process generated for itself. */\n generatedSecrets?: string[];\n}\n\n/** What /ready asks the composition. */\nexport interface RuntimeHealth {\n /**\n * Whether the site can be edited, with short reason codes. Never names the\n * repository: the endpoint is public.\n */\n ready?: () => { ok: boolean; reasons: string[] };\n}\n\nexport interface RuntimeContext {\n config: RuntimeConfig;\n site: SiteContext;\n logger: Logger;\n services: {\n content: ContentStore;\n media: MediaService;\n staging: ContentStagingService;\n };\n limiters: {\n api: RateLimiter;\n };\n operational(): OperationalInfo;\n health: RuntimeHealth;\n shutdown(): Promise<void>;\n}\n\nexport interface CreateRuntimeContextOptions {\n config: RuntimeConfig;\n model: Site;\n /** The working tree's files, without locks. The app builds it. */\n files: ContentFiles;\n /** Revision of the manifest the model was read from. Null when unknown. */\n schemaRevision?: string | null;\n siteId?: string;\n logger?: Logger;\n operational?: () => OperationalInfo;\n health?: RuntimeHealth;\n /** Who owns the working tree. Defaults to the developer. */\n policy?: OwnerPolicy;\n /** Shared with the owner's own work on the tree, such as a sync. */\n locks?: WorkspaceLocks;\n /** See `WorkingTreeStagingDeps.tree`. */\n tree?: GitTree;\n onShutdown?: () => void | Promise<void>;\n}\n\nexport function createRuntimeContext(options: CreateRuntimeContextOptions): RuntimeContext {\n const { config, model } = options;\n\n const logger =\n options.logger ??\n createLogger({\n level: config.LOG_LEVEL,\n pretty: !config.isProduction,\n });\n\n const schemaRevision = options.schemaRevision ?? null;\n\n // Services write through the lock wrapper; staging takes the locks itself.\n const locks = options.locks ?? createWorkspaceLocks();\n\n const readOnly = readOnlyParts(model);\n for (const reason of readOnly.values()) logger.warn(\"Read-only in this version of Mica\", { reason });\n\n const site: SiteContext = {\n id: options.siteId ?? DEFAULT_SITE_ID,\n model,\n contentRepository: lockedFiles(options.files, locks.tree),\n schemaRevision,\n readOnly,\n };\n\n const services = {\n content: createContentStore(model, site.contentRepository, readOnly),\n media: createMediaService({\n files: site.contentRepository,\n maxBytes: config.MAX_UPLOAD_BYTES,\n }),\n staging: createWorkingTreeStagingService({\n model,\n files: options.files,\n contentRoot: config.contentRoot,\n schemaRevision,\n readOnly,\n publishEnabled: config.MICA_LOCAL_PUBLISH,\n logger,\n locks,\n ...(options.policy ? { policy: options.policy } : {}),\n ...(options.tree ? { tree: options.tree } : {}),\n }),\n };\n\n const limiters = {\n // Generous: it stops runaway scripts, it does not shape traffic.\n api: createRateLimiter({ limit: 600, windowMs: 60_000 }),\n };\n\n return {\n config,\n site,\n logger,\n services,\n limiters,\n operational:\n options.operational ??\n (() => ({ auth: \"unspecified\" })),\n health: options.health ?? {},\n\n async shutdown() {\n limiters.api.stop();\n await options.onShutdown?.();\n },\n };\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { createRuntimeContext, DEFAULT_SITE_ID, type CreateRuntimeContextOptions
|
|
|
7
7
|
export { bool, ConfigError, deriveRuntime, describeConfigIssues, list, loadEnvFile, LOOPBACK, runtimeEnv, withoutBlanks, type RuntimeConfig, type RuntimeEnv, } from "./config.js";
|
|
8
8
|
export { startServer, type StartServerOptions } from "./server.js";
|
|
9
9
|
export { actorOf, clientIp, intParam, type Env } from "./http.js";
|
|
10
|
-
export { checkSchema, loadSchema, SchemaError, schemaFingerprint, schemaOrigin } from "./schema.js";
|
|
10
|
+
export { checkSchema, loadSchema, readOnlyParts, SchemaError, schemaFingerprint, schemaOrigin } from "./schema.js";
|
|
11
11
|
export { allowedOrigins, errorHandler, originGuard, rateLimit, requestContext, securityHeaders, } from "./middleware/index.js";
|
|
12
12
|
export { adminAssetsExist } from "./routes/admin-spa.js";
|
|
13
13
|
export { ApiError, badRequest, conflict, forbidden, notFound, unauthenticated, validationFailed, type ApiErrorBody, type ErrorCode, } from "./lib/errors.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACxF,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,KAAK,2BAA2B,EAChC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,IAAI,EACJ,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,UAAU,EACV,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { createRuntimeContext, DEFAULT_SITE_ID, } from "./context.js";
|
|
|
7
7
|
export { bool, ConfigError, deriveRuntime, describeConfigIssues, list, loadEnvFile, LOOPBACK, runtimeEnv, withoutBlanks, } from "./config.js";
|
|
8
8
|
export { startServer } from "./server.js";
|
|
9
9
|
export { actorOf, clientIp, intParam } from "./http.js";
|
|
10
|
-
export { checkSchema, loadSchema, SchemaError, schemaFingerprint, schemaOrigin } from "./schema.js";
|
|
10
|
+
export { checkSchema, loadSchema, readOnlyParts, SchemaError, schemaFingerprint, schemaOrigin } from "./schema.js";
|
|
11
11
|
export { allowedOrigins, errorHandler, originGuard, rateLimit, requestContext, securityHeaders, } from "./middleware/index.js";
|
|
12
12
|
export { adminAssetsExist } from "./routes/admin-spa.js";
|
|
13
13
|
export { ApiError, badRequest, conflict, forbidden, notFound, unauthenticated, validationFailed, } from "./lib/errors.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAA2C,MAAM,UAAU,CAAC;AACxF,OAAO,EACL,oBAAoB,EACpB,eAAe,GAMhB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,IAAI,EACJ,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,UAAU,EACV,aAAa,GAGd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAA2B,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAY,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAA2C,MAAM,UAAU,CAAC;AACxF,OAAO,EACL,oBAAoB,EACpB,eAAe,GAMhB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,IAAI,EACJ,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,UAAU,EACV,aAAa,GAGd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAA2B,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAY,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnH,OAAO,EACL,cAAc,EACd,YAAY,EACZ,WAAW,EACX,SAAS,EACT,cAAc,EACd,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,eAAe,EACf,gBAAgB,GAGjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,YAAY,EAAe,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAoB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EACL,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,eAAe,GAShB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,+BAA+B,GAEhC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAqC,MAAM,qBAAqB,CAAC;AACzG,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,eAAe,GAIhB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,UAAU,GAMX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,QAAQ,EACR,SAAS,GAIV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,WAAW,GAIZ,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,EAAE,eAAe,EAA+B,MAAM,0BAA0B,CAAC;AACxF,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,MAAM,EACN,eAAe,EACf,OAAO,EACP,UAAU,EACV,WAAW,EACX,aAAa,GAGd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * @micacms/runtime: the single-site, authentication-neutral Mica runtime.\n * It must never import an auth implementation; the composing app supplies the `AuthSurface`.\n */\n\nexport { createApp, allowAll, type AuthSurface, type CreateAppOptions } from \"./app.js\";\nexport {\n createRuntimeContext,\n DEFAULT_SITE_ID,\n type CreateRuntimeContextOptions,\n type OperationalInfo,\n type RuntimeContext,\n type RuntimeHealth,\n type SiteContext,\n} from \"./context.js\";\nexport {\n bool,\n ConfigError,\n deriveRuntime,\n describeConfigIssues,\n list,\n loadEnvFile,\n LOOPBACK,\n runtimeEnv,\n withoutBlanks,\n type RuntimeConfig,\n type RuntimeEnv,\n} from \"./config.js\";\nexport { startServer, type StartServerOptions } from \"./server.js\";\nexport { actorOf, clientIp, intParam, type Env } from \"./http.js\";\nexport { checkSchema, loadSchema, readOnlyParts, SchemaError, schemaFingerprint, schemaOrigin } from \"./schema.js\";\nexport {\n allowedOrigins,\n errorHandler,\n originGuard,\n rateLimit,\n requestContext,\n securityHeaders,\n} from \"./middleware/index.js\";\nexport { adminAssetsExist } from \"./routes/admin-spa.js\";\nexport {\n ApiError,\n badRequest,\n conflict,\n forbidden,\n notFound,\n unauthenticated,\n validationFailed,\n type ApiErrorBody,\n type ErrorCode,\n} from \"./lib/errors.js\";\nexport { createLogger, type Logger } from \"./lib/logger.js\";\nexport { createRateLimiter, type RateLimiter } from \"./lib/rate-limit.js\";\nexport { randomToken } from \"./lib/id.js\";\nexport { DEFAULT_BASE_URL, DEFAULT_PORT } from \"./lib/ports.js\";\nexport { VERSION } from \"./lib/version.js\";\nexport {\n analyseChanges,\n classifyPath,\n describeProblems,\n flattenProblems,\n type AnnotatedChange,\n type ChangeOwner,\n type ContentStagingService,\n type RepositoryInfo,\n type PublishInput,\n type StagingProblem,\n type StagingStatus,\n type StagingValidation,\n} from \"./services/changes.js\";\nexport {\n createWorkingTreeStagingService,\n type WorkingTreeStagingDeps,\n} from \"./services/staging.js\";\nexport { createMediaService, formatBytes, type MediaFile, type MediaService } from \"./services/media.js\";\nexport {\n createContentStore,\n ContentError,\n ValidationError,\n type ContentStore,\n type SavedEntry,\n type SaveEntryInput,\n} from \"./services/content-store.js\";\nexport {\n checkChangePaths,\n ConflictError,\n contentFiles,\n FilesError,\n type Author,\n type ContentFiles,\n type ContentFilesOptions,\n type FileChange,\n type FileChanges,\n} from \"./workspace/files.js\";\nexport {\n developerEnvironment,\n firstLine,\n GitError,\n gitRunner,\n type GitCallOptions,\n type GitRunner,\n type GitRunnerOptions,\n} from \"./workspace/git.js\";\nexport {\n createMutex,\n createReadWriteLock,\n createWorkspaceLocks,\n lockedFiles,\n type Mutex,\n type ReadWriteLock,\n type WorkspaceLocks,\n} from \"./workspace/locks.js\";\nexport type {\n OwnerPolicy,\n PublishAvailability,\n RepositoryDescription,\n SyncConflict,\n SyncStatus,\n} from \"./workspace/policy.js\";\nexport { developerPolicy, type DeveloperPolicyOptions } from \"./workspace/developer.js\";\nexport {\n commitPaths,\n headRevision,\n headSource,\n inRepo,\n listTreeChanges,\n LITERAL,\n locateTree,\n parseStatus,\n recommitPaths,\n type GitTree,\n type TreeChange,\n} from \"./workspace/tree.js\";\nexport { schemaStats } from \"./lib/schema-stats.js\";\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* these routes only parse and serialize. The API path is the repository path,
|
|
4
4
|
* Git is the history, and `revision` makes concurrent edits safe.
|
|
5
5
|
*/
|
|
6
|
-
import { ValidationError } from "../services/content-store.js";
|
|
6
|
+
import { ReadOnlyError, ValidationError } from "../services/content-store.js";
|
|
7
7
|
import { ConflictError } from "../workspace/files.js";
|
|
8
8
|
import { collectionEntryPath, compareEntries, entryTitle, singletonPath } from "@micacms/core";
|
|
9
9
|
import { Hono } from "hono";
|
|
@@ -30,7 +30,7 @@ async function handled(run) {
|
|
|
30
30
|
return await run();
|
|
31
31
|
}
|
|
32
32
|
catch (error) {
|
|
33
|
-
if (error instanceof ConflictError)
|
|
33
|
+
if (error instanceof ConflictError || error instanceof ReadOnlyError)
|
|
34
34
|
throw new ApiError("conflict", error.message);
|
|
35
35
|
if (error instanceof ValidationError) {
|
|
36
36
|
throw new ApiError("validation_failed", error.message, { issues: error.issues });
|
|
@@ -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 { 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) 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, 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-media.d.ts","sourceRoot":"","sources":["../../src/routes/admin-media.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,wBAAgB,gBAAgB,IAAI,IAAI,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"admin-media.d.ts","sourceRoot":"","sources":["../../src/routes/admin-media.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,wBAAgB,gBAAgB,IAAI,IAAI,CAAC,GAAG,CAAC,CAmF5C"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* An upload is written on its own, before the entry save, because the editor needs its path first.
|
|
4
4
|
*/
|
|
5
5
|
import { Hono } from "hono";
|
|
6
|
-
import { badRequest, notFound } from "../lib/errors.js";
|
|
6
|
+
import { ApiError, badRequest, notFound } from "../lib/errors.js";
|
|
7
7
|
import { isImageType } from "../lib/file-type.js";
|
|
8
8
|
import { MAX_FILES_PER_UPLOAD } from "../middleware/index.js";
|
|
9
9
|
export function adminMediaRoutes() {
|
|
@@ -29,6 +29,11 @@ export function adminMediaRoutes() {
|
|
|
29
29
|
const accept = form.get("accept");
|
|
30
30
|
// An entry's own `media/` directory or the shared one; the media service refuses anything else.
|
|
31
31
|
const directory = typeof form.get("directory") === "string" ? String(form.get("directory")) : undefined;
|
|
32
|
+
// The media service accepts `/gallery/x/media` too; take the owner from the path it will write to.
|
|
33
|
+
const owner = (directory ?? "").replace(/^[/\\]+/, "").split(/[/\\]/)[0] ?? "";
|
|
34
|
+
const readOnly = ctx.site.readOnly.get(`collections.${owner}`) ?? ctx.site.readOnly.get(`singletons.${owner}`);
|
|
35
|
+
if (readOnly)
|
|
36
|
+
throw new ApiError("conflict", readOnly);
|
|
32
37
|
const prepared = await Promise.all(files.map((file) => ctx.services.media.prepare({
|
|
33
38
|
file,
|
|
34
39
|
directory,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-media.js","sourceRoot":"","sources":["../../src/routes/admin-media.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin-media.js","sourceRoot":"","sources":["../../src/routes/admin-media.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin media routes. Media is files in the content repository, and there is no delete endpoint.\n * An upload is written on its own, before the entry save, because the editor needs its path first.\n */\n\nimport { Hono } from \"hono\";\n\nimport { ApiError, badRequest, notFound } from \"../lib/errors.js\";\nimport { isImageType } from \"../lib/file-type.js\";\nimport { MAX_FILES_PER_UPLOAD } from \"../middleware/index.js\";\nimport type { Env } from \"../http.js\";\n\nexport function adminMediaRoutes(): Hono<Env> {\n const app = new Hono<Env>();\n\n app.get(\"/\", async (c) => {\n const { media } = c.get(\"ctx\").services;\n return c.json({ items: await media.list() });\n });\n\n app.post(\"/\", async (c) => {\n const ctx = c.get(\"ctx\");\n const form = await c.req.formData().catch(() => {\n throw badRequest(\"Expected a multipart upload.\");\n });\n\n // The cast picks one of the two structurally different `File` types (Node's and undici's).\n const files = form\n .getAll(\"file\")\n .filter((value) => typeof value !== \"string\") as unknown as File[];\n\n if (files.length === 0) throw badRequest(\"No file was uploaded.\");\n if (files.length > MAX_FILES_PER_UPLOAD) {\n throw badRequest(`Upload at most ${MAX_FILES_PER_UPLOAD} files at a time.`);\n }\n\n const accept = form.get(\"accept\");\n // An entry's own `media/` directory or the shared one; the media service refuses anything else.\n const directory = typeof form.get(\"directory\") === \"string\" ? String(form.get(\"directory\")) : undefined;\n // The media service accepts `/gallery/x/media` too; take the owner from the path it will write to.\n const owner = (directory ?? \"\").replace(/^[/\\\\]+/, \"\").split(/[/\\\\]/)[0] ?? \"\";\n const readOnly = ctx.site.readOnly.get(`collections.${owner}`) ?? ctx.site.readOnly.get(`singletons.${owner}`);\n if (readOnly) throw new ApiError(\"conflict\", readOnly);\n\n const prepared = await Promise.all(\n files.map((file) =>\n ctx.services.media.prepare({\n file,\n directory,\n accept: accept === \"image\" || accept === \"document\" ? accept : \"any\",\n }),\n ),\n );\n\n await ctx.site.contentRepository.apply({\n changes: prepared.map((item) => ({\n type: \"write\" as const,\n path: item.file.path,\n contents: item.contents,\n })),\n });\n\n return c.json({ items: prepared.map((item) => item.file) }, 201);\n });\n\n /**\n * Serves a file to the editor, behind the admin gate. The site build reads\n * the repository, not this route.\n */\n app.get(\"/file/*\", async (c) => {\n const { media } = c.get(\"ctx\").services;\n const mediaPath = c.req.path.replace(/^.*\\/file\\//, \"\");\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(mediaPath);\n } catch {\n throw badRequest(\"The file path is not valid.\");\n }\n\n const found = await media.read(decoded);\n if (!found) throw notFound(\"No such file.\");\n\n return c.body(found.bytes as unknown as ArrayBuffer, 200, {\n // Text without a charset is decoded by guessing; the repository's text is UTF-8.\n \"content-type\": found.mimeType.startsWith(\"text/\") ? `${found.mimeType}; charset=utf-8` : found.mimeType,\n \"cache-control\": \"private, max-age=60\",\n // A file opened directly runs nothing and reaches nothing on this origin.\n \"content-security-policy\": \"sandbox; default-src 'none'\",\n // An image is shown as a preview; anything else is downloaded, never rendered here.\n ...(isImageType(found.mimeType) ? {} : { \"content-disposition\": \"attachment\" }),\n });\n });\n\n return app;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CAkI7C"}
|
|
@@ -25,12 +25,14 @@ export function adminSystemRoutes() {
|
|
|
25
25
|
slugField: resolveSlugField(def),
|
|
26
26
|
listedFields: listedFields(def),
|
|
27
27
|
fields: def.fields,
|
|
28
|
+
readOnly: site.readOnly.get(`collections.${def.name}`) ?? null,
|
|
28
29
|
})),
|
|
29
30
|
singletons: Object.values(site.model.singletons).map((def) => ({
|
|
30
31
|
name: def.name,
|
|
31
32
|
label: def.label,
|
|
32
33
|
description: def.description ?? null,
|
|
33
34
|
fields: def.fields,
|
|
35
|
+
readOnly: site.readOnly.get(`singletons.${def.name}`) ?? null,
|
|
34
36
|
})),
|
|
35
37
|
});
|
|
36
38
|
});
|
|
@@ -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 })),\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"]}
|
|
1
|
+
{"version":3,"file":"admin-system.js","sourceRoot":"","sources":["../../src/routes/admin-system.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlF,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAO,CAAC;IAE5B,wFAAwF;IACxF,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;QACvB,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAE9B,OAAO,CAAC,CAAC,IAAI,CAAC;YACZ,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC/D,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,KAAK;gBAC7C,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;gBACpC,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,KAAK;gBAC/B,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;gBACpC,UAAU,EAAE,iBAAiB,CAAC,GAAG,CAAC;gBAClC,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC;gBAChC,YAAY,EAAE,YAAY,CAAC,GAAG,CAAC;gBAC/B,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI;aAC/D,CAAC,CAAC;YACH,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC7D,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;gBACpC,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI;aAC9D,CAAC,CAAC;SACJ,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC/B,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC;QAExC,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACnC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YAC5D,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,KAAK,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;SAC7C,CAAC,CAAC,CACJ,CAAC;QAEF,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QAEjC,OAAO,CAAC,CAAC,IAAI,CAAC;YACZ,WAAW;YACX,KAAK,EAAE;gBACL,KAAK,EAAE,KAAK,CAAC,MAAM;gBACnB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBACtD,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;aACvE;YACD,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM;SAC9C,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QAEvB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAC/B,MAAM,OAAO,CAAC,GAAG,CACf,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAC3C,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAU,CACvF,CACF,CACF,CAAC;QAEF,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEnE,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAEnD,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QAE3D,OAAO,CAAC,CAAC,IAAI,CAAC;YACZ,GAAG,EAAE;gBACH,OAAO,EAAE,OAAO;gBAChB,WAAW,EAAE,MAAM,CAAC,QAAQ;gBAC5B,IAAI,EAAE,OAAO,CAAC,OAAO;gBACrB,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE;gBAC/C,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;gBACpE,aAAa;aACd;YACD,MAAM,EAAE;gBACN,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBAC5C,GAAG,IAAI;oBACP,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;iBAChC,CAAC,CAAC;gBACH,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B;YACD,UAAU,EAAE;gBACV,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM;gBACzC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC;gBACrE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,EAAE;gBACpF,GAAG,UAAU;aACd;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE;gBACjB,OAAO,EAAE,MAAM,CAAC,eAAe;gBAC/B,YAAY,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC;gBACtD,wEAAwE;gBACxE,WAAW,EAAE,UAAU,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;gBACzE,OAAO,EAAE,WAAW,CAAC,OAAO,IAAI,IAAI;gBACpC,QAAQ,EAAE,MAAM,CAAC,SAAS;gBAC1B,cAAc,EAAE,MAAM,CAAC,gBAAgB;gBACvC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,gBAAgB,CAAC;gBACvD,eAAe,EAAE,WAAW,CAAC,eAAe,IAAI,IAAI;gBACpD,gBAAgB,EAAE,WAAW,CAAC,gBAAgB,IAAI,IAAI;gBACtD,IAAI,EAAE,WAAW,CAAC,IAAI;gBACtB,SAAS,EAAE,WAAW,CAAC,SAAS,IAAI,IAAI;gBACxC,UAAU,EAAE,MAAM,CAAC,WAAW;gBAC9B,YAAY,EAAE,MAAM,CAAC,YAAY;gBACjC,gBAAgB,EAAE,WAAW,CAAC,gBAAgB,IAAI,EAAE;gBACpD,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,GAAG;aACvC;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC","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"]}
|
package/dist/schema.d.ts
CHANGED
|
@@ -15,11 +15,17 @@ export declare function schemaOrigin(source: ContentSource): string;
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function loadSchema(source: ContentSource): Promise<Site>;
|
|
17
17
|
/**
|
|
18
|
-
* Runs the `defineSite` checks
|
|
19
|
-
*
|
|
20
|
-
* startup error, not a later failure on an editor's screen.
|
|
18
|
+
* Runs the `defineSite` checks on untrusted input. A newer `protocolVersion` is
|
|
19
|
+
* refused; an unknown field kind is not, and `readOnlyParts` names what it locks.
|
|
21
20
|
*/
|
|
22
21
|
export declare function checkSchema(raw: unknown, origin?: string): Site;
|
|
22
|
+
/**
|
|
23
|
+
* The collections and singletons this build of Mica must not write, keyed
|
|
24
|
+
* `collections.<name>` or `singletons.<name>`, with the reason for the editor.
|
|
25
|
+
* A field kind from a newer @micacms/astro makes only its own collection or
|
|
26
|
+
* singleton read-only; Mica still starts, and the rest stays editable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readOnlyParts(site: Site): Map<string, string>;
|
|
23
29
|
/**
|
|
24
30
|
* A short digest of a model, to show drift. Field order counts, because it is
|
|
25
31
|
* the order of the admin form.
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
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"}
|
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, isFieldKind, isProtocolManifest, MANIFEST_PATH, PROTOCOL_VERSION, } from "@micacms/core";
|
|
6
|
+
import { checkCompatibility, defineSite, deriveLayout, recordLayout, isFieldKind, isProtocolManifest, MANIFEST_PATH, PROTOCOL_VERSION, } from "@micacms/core";
|
|
7
7
|
export class SchemaError extends Error {
|
|
8
8
|
name = "SchemaError";
|
|
9
9
|
}
|
|
@@ -46,9 +46,8 @@ function describeMissing(origin) {
|
|
|
46
46
|
` then commit ${MANIFEST_PATH} and push it to the branch Mica is reading.`);
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Runs the `defineSite` checks
|
|
50
|
-
*
|
|
51
|
-
* startup error, not a later failure on an editor's screen.
|
|
49
|
+
* Runs the `defineSite` checks on untrusted input. A newer `protocolVersion` is
|
|
50
|
+
* refused; an unknown field kind is not, and `readOnlyParts` names what it locks.
|
|
52
51
|
*/
|
|
53
52
|
export function checkSchema(raw, origin = "the schema") {
|
|
54
53
|
if (raw === null || typeof raw !== "object") {
|
|
@@ -86,7 +85,14 @@ export function checkSchema(raw, origin = "the schema") {
|
|
|
86
85
|
delete def.defaultSort;
|
|
87
86
|
}
|
|
88
87
|
try {
|
|
89
|
-
|
|
88
|
+
const site = defineSite({ collections, singletons });
|
|
89
|
+
// Read and classify entries where the manifest says they are, not where fields this build may not know imply.
|
|
90
|
+
for (const def of [...Object.values(site.collections), ...Object.values(site.singletons)]) {
|
|
91
|
+
const stored = def.layout;
|
|
92
|
+
if (stored === "file" || stored === "directory")
|
|
93
|
+
recordLayout(def.fields, stored);
|
|
94
|
+
}
|
|
95
|
+
return site;
|
|
90
96
|
}
|
|
91
97
|
catch (error) {
|
|
92
98
|
throw new SchemaError(`${origin} is not a valid content model.\n ${error.message}`);
|
|
@@ -101,16 +107,45 @@ function assertFieldMap(fields, at, origin) {
|
|
|
101
107
|
if (field === null || typeof field !== "object") {
|
|
102
108
|
throw new SchemaError(`${origin}: field "${here}" is not an object.`);
|
|
103
109
|
}
|
|
104
|
-
if (!isFieldKind(field.kind)) {
|
|
105
|
-
throw new SchemaError(`${origin}: field "${here}" has unknown kind "${String(field.kind)}".\n` +
|
|
106
|
-
` This build of Mica does not support it — the project may have been built\n` +
|
|
107
|
-
` against a newer @micacms/astro than the server is running.`);
|
|
108
|
-
}
|
|
109
110
|
if (field.kind === "object" || field.kind === "repeater") {
|
|
110
111
|
assertFieldMap(field.fields, here, origin);
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* The collections and singletons this build of Mica must not write, keyed
|
|
117
|
+
* `collections.<name>` or `singletons.<name>`, with the reason for the editor.
|
|
118
|
+
* A field kind from a newer @micacms/astro makes only its own collection or
|
|
119
|
+
* singleton read-only; Mica still starts, and the rest stays editable.
|
|
120
|
+
*/
|
|
121
|
+
export function readOnlyParts(site) {
|
|
122
|
+
const parts = new Map();
|
|
123
|
+
const add = (key, def) => {
|
|
124
|
+
const unknown = unknownKinds(def.fields, []);
|
|
125
|
+
const stored = def.layout;
|
|
126
|
+
if (unknown.length > 0) {
|
|
127
|
+
parts.set(key, `"${def.label}" uses ${unknown.map((field) => `"${field.path}" (${field.kind})`).join(", ")}, ` +
|
|
128
|
+
"a field type that this version of Mica does not know. Upgrade Mica to edit it.");
|
|
129
|
+
}
|
|
130
|
+
else if (stored !== undefined && stored !== deriveLayout(def.fields)) {
|
|
131
|
+
parts.set(key, `"${def.label}" is stored as ${String(stored)}, which this version of Mica does not expect. ` +
|
|
132
|
+
"Upgrade Mica to edit it.");
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
for (const [name, def] of Object.entries(site.collections))
|
|
136
|
+
add(`collections.${name}`, def);
|
|
137
|
+
for (const [name, def] of Object.entries(site.singletons))
|
|
138
|
+
add(`singletons.${name}`, def);
|
|
139
|
+
return parts;
|
|
140
|
+
}
|
|
141
|
+
function unknownKinds(fields, at) {
|
|
142
|
+
return Object.entries(fields).flatMap(([key, field]) => {
|
|
143
|
+
const here = [...at, key];
|
|
144
|
+
if (!isFieldKind(field.kind))
|
|
145
|
+
return [{ path: here.join("."), kind: String(field.kind) }];
|
|
146
|
+
return field.kind === "object" || field.kind === "repeater" ? unknownKinds(field.fields, here) : [];
|
|
147
|
+
});
|
|
148
|
+
}
|
|
114
149
|
/**
|
|
115
150
|
* A short digest of a model, to show drift. Field order counts, because it is
|
|
116
151
|
* the order of the admin form.
|
package/dist/schema.js.map
CHANGED
|
@@ -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 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, and also refuses an unknown field `kind`, which\n * `defineSite` does not check. This turns a core version mismatch into a\n * startup error, not a later failure on an editor's screen.\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 return defineSite({ collections, singletons });\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 (!isFieldKind(field.kind)) {\n throw new SchemaError(\n `${origin}: field \"${here}\" has unknown kind \"${String(field.kind)}\".\\n` +\n ` This build of Mica does not support it — the project may have been built\\n` +\n ` against a newer @micacms/astro than the server is running.`,\n );\n }\n if (field.kind === \"object\" || field.kind === \"repeater\") {\n assertFieldMap(field.fields, here, origin);\n }\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":"AAAA;;;;GAIG;AAEH,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,gBAAgB,GAMjB,MAAM,eAAe,CAAC;AAEvB,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,GAAG,aAAa,CAAC;CAC/B;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAqB;IACpD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEpC,IAAI,IAAmB,CAAC;IACxB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yEAAyE;QACzE,MAAM,IAAI,WAAW,CACnB,yCAAyC,MAAM,CAAC,MAAM,KAAK;YACzD,KAAM,KAAe,CAAC,OAAO,IAAI;YACjC,yEAAyE;YACzE,qBAAqB,aAAa,GAAG,CACxC,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,IAAI;QAAE,MAAM,IAAI,WAAW,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAElE,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,uBAAwB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,OAAO,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,eAAe,CAAC,MAAc;IACrC,OAAO,CACL,uBAAuB,MAAM,KAAK;QAClC,+EAA+E;QAC/E,uEAAuE;QACvE,iBAAiB,aAAa,6CAA6C,CAC5E,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY,EAAE,MAAM,GAAG,YAAY;IAC7D,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,GAAG,uBAAuB,CAAC,CAAC;IACnG,CAAC;IAED,2EAA2E;IAC3E,IAAI,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,aAAa,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,aAAa,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YAC1D,MAAM,IAAI,WAAW,CACnB,GAAG,MAAM,6BAA6B,aAAa,CAAC,OAAO,uBAAuB;gBAChF,uBAAuB,gBAAgB,KAAK;gBAC5C,uEAAuE,CAC1E,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,GAAsD,CAAC;IAEzE,IAAI,SAAS,CAAC,WAAW,KAAK,IAAI,IAAI,OAAO,SAAS,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QAChF,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,yEAAyE,CAAC,CAAC;IAC5G,CAAC;IAED,MAAM,aAAa,GAAG,SAAS,CAAC,UAAU,CAAC;IAC3C,IAAI,aAAa,KAAK,SAAS,IAAI,CAAC,aAAa,KAAK,IAAI,IAAI,OAAO,aAAa,KAAK,QAAQ,CAAC,EAAE,CAAC;QACjG,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,8CAA8C,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,WAAW,GAAG,SAAS,CAAC,WAAyC,CAAC;IACxE,MAAM,UAAU,GAAG,CAAC,aAAa,IAAI,EAAE,CAA8B,CAAC;IAEtE,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACtD,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACrD,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IAED,+FAA+F;IAC/F,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC;QACtC,IAAI,KAAK,IAAI,KAAK,KAAK,UAAU,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC,WAAW,CAAC;IACpF,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;QACrD,8GAA8G;QAC9G,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;YAC1F,MAAM,MAAM,GAAI,GAA4B,CAAC,MAAM,CAAC;YACpD,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,WAAW;gBAAE,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,qCAAsC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IAClG,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAAe,EAAE,EAAU,EAAE,MAAc;IACjE,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,MAAM,EAAE,6BAA6B,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAkB,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,GAAG,GAAG,EAAE,IAAI,GAAG,EAAE,CAAC;QAE5B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAChD,MAAM,IAAI,WAAW,CAAC,GAAG,MAAM,YAAY,IAAI,qBAAqB,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACzD,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAU;IACtC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,GAA2B,EAAE,EAAE;QACvD,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAI,GAA4B,CAAC,MAAM,CAAC;QACpD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,GAAG,CACP,GAAG,EACH,IAAI,GAAG,CAAC,KAAK,UAAU,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBAC7F,gFAAgF,CACnF,CAAC;QACJ,CAAC;aAAM,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACvE,KAAK,CAAC,GAAG,CACP,GAAG,EACH,IAAI,GAAG,CAAC,KAAK,kBAAkB,MAAM,CAAC,MAAM,CAAC,gDAAgD;gBAC3F,0BAA0B,CAC7B,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,GAAG,CAAC,eAAe,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5F,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,GAAG,CAAC,cAAc,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IAC1F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAgB,EAAE,EAAY;IAClD,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACrD,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1F,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtG,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAU;IAChD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACnD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACtF,CAAC","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"]}
|
|
@@ -8,6 +8,10 @@ import { FilesError, type ContentFiles } from "../workspace/files.js";
|
|
|
8
8
|
export declare class ContentError extends FilesError {
|
|
9
9
|
name: string;
|
|
10
10
|
}
|
|
11
|
+
/** A write to a collection or singleton that this build of Mica cannot interpret. */
|
|
12
|
+
export declare class ReadOnlyError extends ContentError {
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
11
15
|
export declare class ValidationError extends ContentError {
|
|
12
16
|
readonly issues: FieldIssue[];
|
|
13
17
|
name: string;
|
|
@@ -28,7 +32,8 @@ export interface SavedEntry {
|
|
|
28
32
|
/** True when the save moved the entry's files. */
|
|
29
33
|
renamed: boolean;
|
|
30
34
|
}
|
|
31
|
-
|
|
35
|
+
/** `readOnly` comes from `readOnlyParts`: its collections and singletons are read, never written. */
|
|
36
|
+
export declare function createContentStore(site: Site, workspace: ContentFiles, readOnly?: ReadonlyMap<string, string>): {
|
|
32
37
|
list(collectionName: string): Promise<ContentEntry[]>;
|
|
33
38
|
get(collectionName: string, slug: string): Promise<ContentEntry | null>;
|
|
34
39
|
/** The revision an editor must send back with a save of this entry. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-store.d.ts","sourceRoot":"","sources":["../../src/services/content-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAoBL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,UAAU,EAEf,KAAK,SAAS,EACd,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAiB,UAAU,EAAE,KAAK,YAAY,EAAmB,MAAM,uBAAuB,CAAC;AAEtG,qBAAa,YAAa,SAAQ,UAAU;IACjC,IAAI,SAAkB;CAChC;AAED,qBAAa,eAAgB,SAAQ,YAAY;IAI7C,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE;IAHtB,IAAI,SAAqB;gBAEhC,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,UAAU,EAAE;CAIhC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"content-store.d.ts","sourceRoot":"","sources":["../../src/services/content-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAoBL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,UAAU,EAEf,KAAK,SAAS,EACd,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAiB,UAAU,EAAE,KAAK,YAAY,EAAmB,MAAM,uBAAuB,CAAC;AAEtG,qBAAa,YAAa,SAAQ,UAAU;IACjC,IAAI,SAAkB;CAChC;AAED,qFAAqF;AACrF,qBAAa,aAAc,SAAQ,YAAY;IACpC,IAAI,SAAmB;CACjC;AAED,qBAAa,eAAgB,SAAQ,YAAY;IAI7C,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE;IAHtB,IAAI,SAAqB;gBAEhC,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,UAAU,EAAE;CAIhC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qGAAqG;AACrG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa;yBAmJ1F,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;wBAIjC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAK7E,uEAAuE;+BACtC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQ9E,2EAA2E;8BAC3C,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;uBAKtC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;yBAOvC,MAAM,SAAS,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;2BA8D5D,MAAM,QAChB,MAAM,YACH;QAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAC5C,OAAO,CAAC,IAAI,CAAC;wBAuBR,MAAM,WACH,OAAO,YACP;QAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAC5C,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;4BAwBd,MAAM,SACf,MAAM,EAAE,GACd,OAAO,CAAC,IAAI,CAAC;8BAtSe,MAAM,KAAG,UAAU;6BAMpB,MAAM,KAAG,SAAS;IAoThD,4EAA4E;gCAC1C,MAAM,UAAU,MAAM,WAAW,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK3F,0EAA0E;mCACrC,MAAM,QAAQ,MAAM;oBACpB,MAAM;cAAQ,MAAM;eAAS,MAAM;;EAuB3E;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -9,6 +9,10 @@ import { ConflictError, FilesError } from "../workspace/files.js";
|
|
|
9
9
|
export class ContentError extends FilesError {
|
|
10
10
|
name = "ContentError";
|
|
11
11
|
}
|
|
12
|
+
/** A write to a collection or singleton that this build of Mica cannot interpret. */
|
|
13
|
+
export class ReadOnlyError extends ContentError {
|
|
14
|
+
name = "ReadOnlyError";
|
|
15
|
+
}
|
|
12
16
|
export class ValidationError extends ContentError {
|
|
13
17
|
issues;
|
|
14
18
|
name = "ValidationError";
|
|
@@ -17,7 +21,8 @@ export class ValidationError extends ContentError {
|
|
|
17
21
|
this.issues = issues;
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
|
-
|
|
24
|
+
/** `readOnly` comes from `readOnlyParts`: its collections and singletons are read, never written. */
|
|
25
|
+
export function createContentStore(site, workspace, readOnly = new Map()) {
|
|
21
26
|
const requireCollection = (name) => {
|
|
22
27
|
const def = site.collections[name];
|
|
23
28
|
if (!def)
|
|
@@ -30,6 +35,12 @@ export function createContentStore(site, workspace) {
|
|
|
30
35
|
throw new ContentError(`Unknown singleton "${name}".`);
|
|
31
36
|
return def;
|
|
32
37
|
};
|
|
38
|
+
const writable = (def, key) => {
|
|
39
|
+
const reason = readOnly.get(key);
|
|
40
|
+
if (reason)
|
|
41
|
+
throw new ReadOnlyError(reason);
|
|
42
|
+
return def;
|
|
43
|
+
};
|
|
33
44
|
const validate = (def, data, what) => {
|
|
34
45
|
const result = validateFields(def.fields, data, "storage");
|
|
35
46
|
if (!result.ok) {
|
|
@@ -170,7 +181,7 @@ export function createContentStore(site, workspace) {
|
|
|
170
181
|
return withDefaults(def.fields, data);
|
|
171
182
|
},
|
|
172
183
|
async save(collectionName, input) {
|
|
173
|
-
const def = requireCollection(collectionName);
|
|
184
|
+
const def = writable(requireCollection(collectionName), `collections.${collectionName}`);
|
|
174
185
|
const data = validate(def, input.data, "This entry");
|
|
175
186
|
const slugKey = resolveSlugField(def);
|
|
176
187
|
const requested = pickSlug(def, data, input.slug);
|
|
@@ -212,7 +223,7 @@ export function createContentStore(site, workspace) {
|
|
|
212
223
|
return { slug, title, revision, renamed: previousBase !== null };
|
|
213
224
|
},
|
|
214
225
|
async remove(collectionName, slug, options = {}) {
|
|
215
|
-
const def = requireCollection(collectionName);
|
|
226
|
+
const def = writable(requireCollection(collectionName), `collections.${collectionName}`);
|
|
216
227
|
// One path segment: an empty slug or `a/b` would delete more than one entry.
|
|
217
228
|
if (slug === "" || slug.includes("/") || slug === "." || slug === "..") {
|
|
218
229
|
throw new ContentPathError(`"${slug}" is not an entry of "${collectionName}".`);
|
|
@@ -228,7 +239,7 @@ export function createContentStore(site, workspace) {
|
|
|
228
239
|
});
|
|
229
240
|
},
|
|
230
241
|
async saveSingleton(name, payload, options = {}) {
|
|
231
|
-
const def = requireSingleton(name);
|
|
242
|
+
const def = writable(requireSingleton(name), `singletons.${name}`);
|
|
232
243
|
const data = validate(def, payload, `The "${def.label}" settings`);
|
|
233
244
|
const base = singletonPath(def.fields, name);
|
|
234
245
|
const serialized = serializeEntry(def.fields, mediaForDisk(def.fields, data, base));
|
|
@@ -243,7 +254,7 @@ export function createContentStore(site, workspace) {
|
|
|
243
254
|
return { revision: await revisionAfterWrite(singletonDataPath(def, name), folderOf(def.fields, base)) };
|
|
244
255
|
},
|
|
245
256
|
async reorder(collectionName, slugs) {
|
|
246
|
-
const def = requireCollection(collectionName);
|
|
257
|
+
const def = writable(requireCollection(collectionName), `collections.${collectionName}`);
|
|
247
258
|
if (!def.sortable) {
|
|
248
259
|
throw new ContentError(`The "${def.label}" collection is not manually sortable.`);
|
|
249
260
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-store.js","sourceRoot":"","sources":["../../src/services/content-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Collections and singletons over the working tree's files: validation, slugs,\n * entry files and `_order.json`. An entry is its slug and path, Git keeps the\n * history, a save is a draft, and only Publish makes a commit.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport {\n ContentPathError,\n DOCUMENT_EXTENSION,\n INDEX_FILE,\n ORDER_FILE,\n collectionEntryPath,\n entryFiles,\n entryLayout,\n entryTitle,\n isValidSlug,\n readCollection,\n readSingleton,\n relativizeMedia,\n resolveSlugField,\n serializeEntry,\n singletonPath,\n slugify,\n uniqueSlug,\n validateFields,\n withDefaults,\n type Collection,\n type ContentEntry,\n type FieldIssue,\n type FieldMap,\n type Singleton,\n type Site,\n} from \"@micacms/core\";\n\nimport { ConflictError, FilesError, type ContentFiles, type FileChange } from \"../workspace/files.js\";\n\nexport class ContentError extends FilesError {\n override name = \"ContentError\";\n}\n\nexport class ValidationError extends ContentError {\n override name = \"ValidationError\";\n constructor(\n message: string,\n readonly issues: FieldIssue[],\n ) {\n super(message);\n }\n}\n\nexport interface SaveEntryInput {\n data: Record<string, unknown>;\n /** The entry being replaced. Absent when creating. */\n slug?: string;\n /** The revision `slug` had when the editor opened it. Absent writes unconditionally. */\n expectedRevision?: string | null;\n}\n\nexport interface SavedEntry {\n slug: string;\n title: string;\n /** The entry's revision after this save, for the next save. A file revision, not a commit. */\n revision: string;\n /** True when the save moved the entry's files. */\n renamed: boolean;\n}\n\nexport function createContentStore(site: Site, workspace: ContentFiles) {\n const requireCollection = (name: string): Collection => {\n const def = site.collections[name];\n if (!def) throw new ContentError(`Unknown collection \"${name}\".`);\n return def;\n };\n\n const requireSingleton = (name: string): Singleton => {\n const def = site.singletons[name];\n if (!def) throw new ContentError(`Unknown singleton \"${name}\".`);\n return def;\n };\n\n const validate = (\n def: Collection | Singleton,\n data: unknown,\n what: string,\n ): Record<string, unknown> => {\n const result = validateFields(def.fields, data, \"storage\");\n if (!result.ok) {\n throw new ValidationError(\n `${what} cannot be saved until these fields are correct.`,\n result.issues,\n );\n }\n return result.data as Record<string, unknown>;\n };\n\n /**\n * Deletes for `.mdoc` files not in `keep`, so a cleared rich-text field does\n * not come back on the next read. Media stays: an unreferenced upload is not\n * garbage.\n */\n const staleDocuments = async (base: string, keep: Set<string>): Promise<FileChange[]> => {\n const changes: FileChange[] = [];\n\n for (const item of await workspace.list(base)) {\n if (item.type !== \"file\") continue;\n if (!item.name.endsWith(DOCUMENT_EXTENSION)) continue;\n if (keep.has(item.name)) continue;\n changes.push({ type: \"delete\", path: `${base}/${item.name}` });\n }\n\n return changes;\n };\n\n /**\n * The document's revision after a write. Throws when the file is absent,\n * because an empty token could never match.\n */\n const revisionAfterWrite = async (dataFile: string, folder: string | null): Promise<string> => {\n const revision = await documentRevision(dataFile, folder);\n if (revision === null) {\n throw new ContentError(`Saved, but \"${dataFile}\" is not in the repository afterwards.`);\n }\n return revision;\n };\n\n /** The folder whose files make up one document, or null for a one-file document. */\n const folderOf = (fields: FieldMap, base: string): string | null =>\n entryLayout(fields) === \"directory\" ? base : null;\n\n /**\n * The revision of a whole document: its data file and, in the folder layout,\n * every file of its folder. A change to any one of them makes the next save a\n * conflict, not an overwrite.\n */\n const documentRevision = async (dataFile: string, folder: string | null): Promise<string | null> => {\n const files = await documentFiles(dataFile, folder);\n if (files === null) return null;\n if (folder === null) return files.get(dataFile)!;\n const parts = [...files].map(([path, revision]) => `${path}\\u0000${revision}`);\n return createHash(\"sha256\").update(parts.sort().join(\"\\n\")).digest(\"hex\").slice(0, 16);\n };\n\n /** Every file of a document with its revision, or null when its data file is absent. */\n const documentFiles = async (dataFile: string, folder: string | null): Promise<Map<string, string> | null> => {\n const data = await workspace.revision(dataFile);\n if (data === null) return null;\n const files = new Map([[dataFile, data]]);\n if (folder === null) return files;\n const walk = async (dir: string): Promise<void> => {\n for (const item of await workspace.list(dir)) {\n const child = `${dir}/${item.name}`;\n if (item.type === \"directory\") await walk(child);\n else files.set(child, (await workspace.revision(child)) ?? \"\");\n }\n };\n await walk(folder);\n return files;\n };\n\n /**\n * Refuses when the document changed since the editor read `expected`, and\n * returns every file's revision for `apply`, which checks them all again\n * under the write lock.\n */\n const expectDocument = async (\n expected: string | null | undefined,\n dataFile: string,\n folder: string | null,\n ): Promise<Record<string, string | null> | undefined> => {\n if (expected === undefined) return undefined;\n const files = await documentFiles(dataFile, folder);\n if ((await documentRevision(dataFile, folder)) !== expected) {\n throw new ConflictError(\"This content changed after you opened it. Reload to see the current version.\", [\n dataFile,\n ]);\n }\n return files === null ? { [dataFile]: null } : Object.fromEntries(files);\n };\n\n /**\n * The order file with a renamed slug in its old place, so the entry does not\n * drop to the end of the list.\n */\n const renameInOrder = async (name: string, from: string, to: string): Promise<FileChange | null> => {\n const path = `${name}/${ORDER_FILE}`;\n const raw = await workspace.read(path);\n if (raw === null) return null;\n\n let listed: unknown;\n try {\n listed = JSON.parse(raw);\n } catch {\n // Reading the collection reports a malformed order file; a save need not.\n return null;\n }\n if (!Array.isArray(listed) || !listed.includes(from)) return null;\n\n const order = listed.map((slug: unknown) => (slug === from ? to : slug));\n return { type: \"write\", path, contents: `${JSON.stringify(order, null, 2)}\\n` };\n };\n\n /** Slugs already taken in a collection, so a new one can be made unique. */\n const takenSlugs = async (def: Collection, name: string, except?: string): Promise<string[]> => {\n const entries = await readCollection(workspace, def, name);\n return entries.map((entry) => entry.slug).filter((slug) => slug !== except);\n };\n\n return {\n async list(collectionName: string): Promise<ContentEntry[]> {\n return readCollection(workspace, requireCollection(collectionName), collectionName);\n },\n\n async get(collectionName: string, slug: string): Promise<ContentEntry | null> {\n const entries = await this.list(collectionName);\n return entries.find((entry) => entry.slug === slug) ?? null;\n },\n\n /** The revision an editor must send back with a save of this entry. */\n async revisionOf(collectionName: string, slug: string): Promise<string | null> {\n const def = requireCollection(collectionName);\n return documentRevision(\n dataPath(def, collectionName, slug),\n folderOf(def.fields, collectionEntryPath(def.fields, collectionName, slug)),\n );\n },\n\n /** The revision an editor must send back with a save of this singleton. */\n async revisionOfSingleton(name: string): Promise<string | null> {\n const def = requireSingleton(name);\n return documentRevision(singletonDataPath(def, name), folderOf(def.fields, singletonPath(def.fields, name)));\n },\n\n async getSingleton(name: string): Promise<Record<string, unknown>> {\n const def = requireSingleton(name);\n const data = await readSingleton(workspace, def, name);\n // A singleton with no file yet opens as an empty form, not a 404.\n return withDefaults(def.fields, data);\n },\n\n async save(collectionName: string, input: SaveEntryInput): Promise<SavedEntry> {\n const def = requireCollection(collectionName);\n const data = validate(def, input.data, \"This entry\");\n\n const slugKey = resolveSlugField(def)!;\n const requested = pickSlug(def, data, input.slug);\n const slug =\n requested === input.slug\n ? requested\n : uniqueSlug(requested, await takenSlugs(def, collectionName, input.slug));\n\n data[slugKey] = slug;\n\n const base = collectionEntryPath(def.fields, collectionName, slug);\n const previousBase =\n input.slug && input.slug !== slug\n ? collectionEntryPath(def.fields, collectionName, input.slug)\n : null;\n\n // The editor uses root-relative media paths; store them relative to the\n // entry (the old directory on a rename).\n const serialized = serializeEntry(\n def.fields,\n mediaForDisk(def.fields, data, previousBase ?? base),\n );\n const files = entryFiles(def.fields, base, serialized);\n\n const changes: FileChange[] = Object.entries(files).map(([path, contents]) => ({\n type: \"write\",\n path,\n contents,\n }));\n\n if (previousBase) {\n changes.push(\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: previousBase }\n : { type: \"delete\", path: previousBase },\n );\n await moveMedia(workspace, previousBase, base, changes);\n const order = await renameInOrder(collectionName, input.slug!, slug);\n if (order) changes.push(order);\n } else if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n const title = entryTitle(def, data);\n await workspace.apply({\n changes,\n expect: await expectDocument(\n input.expectedRevision,\n dataPath(def, collectionName, input.slug ?? slug),\n folderOf(def.fields, previousBase ?? base),\n ),\n });\n\n const revision = await revisionAfterWrite(dataPath(def, collectionName, slug), folderOf(def.fields, base));\n\n return { slug, title, revision, renamed: previousBase !== null };\n },\n\n async remove(\n collectionName: string,\n slug: string,\n options: { expectedRevision?: string | null } = {},\n ): Promise<void> {\n const def = requireCollection(collectionName);\n // One path segment: an empty slug or `a/b` would delete more than one entry.\n if (slug === \"\" || slug.includes(\"/\") || slug === \".\" || slug === \"..\") {\n throw new ContentPathError(`\"${slug}\" is not an entry of \"${collectionName}\".`);\n }\n const base = collectionEntryPath(def.fields, collectionName, slug);\n\n await workspace.apply({\n changes: [\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: base }\n : { type: \"delete\", path: base },\n ],\n expect: await expectDocument(\n options.expectedRevision,\n dataPath(def, collectionName, slug),\n folderOf(def.fields, base),\n ),\n });\n },\n\n async saveSingleton(\n name: string,\n payload: unknown,\n options: { expectedRevision?: string | null } = {},\n ): Promise<{ revision: string }> {\n const def = requireSingleton(name);\n const data = validate(def, payload, `The \"${def.label}\" settings`);\n\n const base = singletonPath(def.fields, name);\n const serialized = serializeEntry(def.fields, mediaForDisk(def.fields, data, base));\n\n const changes: FileChange[] = Object.entries(entryFiles(def.fields, base, serialized)).map(\n ([path, contents]) => ({ type: \"write\", path, contents }),\n );\n\n if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n await workspace.apply({\n changes,\n expect: await expectDocument(options.expectedRevision, singletonDataPath(def, name), folderOf(def.fields, base)),\n });\n\n return { revision: await revisionAfterWrite(singletonDataPath(def, name), folderOf(def.fields, base)) };\n },\n\n async reorder(\n collectionName: string,\n slugs: string[],\n ): Promise<void> {\n const def = requireCollection(collectionName);\n if (!def.sortable) {\n throw new ContentError(`The \"${def.label}\" collection is not manually sortable.`);\n }\n\n await workspace.apply({\n changes: [\n {\n type: \"write\",\n path: `${collectionName}/${ORDER_FILE}`,\n contents: `${JSON.stringify(slugs, null, 2)}\\n`,\n },\n ],\n });\n },\n\n requireCollection,\n requireSingleton,\n\n /** Slug the server would assign, so the editor's URL preview matches it. */\n async suggestSlug(collectionName: string, source: string, except?: string): Promise<string> {\n const def = requireCollection(collectionName);\n return uniqueSlug(slugify(source), await takenSlugs(def, collectionName, except));\n },\n\n /** Entries pointing at `slug`, so the editor can warn before a delete. */\n async findReferences(collectionName: string, slug: string) {\n const results: Array<{ collection: string; slug: string; title: string }> = [];\n\n for (const [name, def] of Object.entries(site.collections)) {\n const keys = Object.entries(def.fields)\n .filter(([, field]) => field.kind === \"relation\" && field.collection === collectionName)\n .map(([key]) => key);\n\n if (keys.length === 0) continue;\n\n for (const entry of await readCollection(workspace, def, name)) {\n const points = keys.some((key) => {\n const value = entry.data[key];\n return Array.isArray(value) ? value.includes(slug) : value === slug;\n });\n if (points) {\n results.push({ collection: name, slug: entry.slug, title: entryTitle(def, entry.data) });\n }\n }\n }\n\n return results;\n },\n };\n}\n\nexport type ContentStore = ReturnType<typeof createContentStore>;\n\n/**\n * Media paths as the repository stores them. Only a directory entry has\n * colocated media to relativize.\n */\nfunction mediaForDisk(\n fields: FieldMap,\n data: Record<string, unknown>,\n base: string,\n): Record<string, unknown> {\n return entryLayout(fields) === \"directory\" ? relativizeMedia(fields, data, base) : data;\n}\n\nfunction dataPath(def: Collection, collection: string, slug: string): string {\n const base = collectionEntryPath(def.fields, collection, slug);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\nfunction singletonDataPath(def: Singleton, name: string): string {\n const base = singletonPath(def.fields, name);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\n/** The slug from the payload's slug field, or one derived from the title. */\nfunction pickSlug(\n def: Collection,\n data: Record<string, unknown>,\n current: string | undefined,\n): string {\n const slugKey = resolveSlugField(def)!;\n const fromData = data[slugKey];\n\n const candidate =\n (typeof fromData === \"string\" && fromData.trim()) || slugify(entryTitle(def, data));\n\n if (!candidate) {\n throw new ContentError(\"An entry needs a title or a URL before it can be saved.\");\n }\n\n const normalised = isValidSlug(candidate) ? candidate : slugify(candidate);\n if (!normalised) {\n throw new ContentError(`\"${candidate}\" cannot be turned into a URL. Use letters and numbers.`);\n }\n\n return normalised === current ? current : normalised;\n}\n\n/** Copies colocated media to the renamed entry; the caller deletes the old directory. */\nasync function moveMedia(\n files: ContentFiles,\n from: string,\n to: string,\n changes: FileChange[],\n): Promise<void> {\n for (const item of await files.list(`${from}/media`)) {\n if (item.type !== \"file\") continue;\n\n const bytes = await files.readBinary(`${from}/media/${item.name}`);\n if (bytes === null) continue;\n\n changes.push({ type: \"write\", path: `${to}/media/${item.name}`, contents: bytes });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"content-store.js","sourceRoot":"","sources":["../../src/services/content-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Collections and singletons over the working tree's files: validation, slugs,\n * entry files and `_order.json`. An entry is its slug and path, Git keeps the\n * history, a save is a draft, and only Publish makes a commit.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport {\n ContentPathError,\n DOCUMENT_EXTENSION,\n INDEX_FILE,\n ORDER_FILE,\n collectionEntryPath,\n entryFiles,\n entryLayout,\n entryTitle,\n isValidSlug,\n readCollection,\n readSingleton,\n relativizeMedia,\n resolveSlugField,\n serializeEntry,\n singletonPath,\n slugify,\n uniqueSlug,\n validateFields,\n withDefaults,\n type Collection,\n type ContentEntry,\n type FieldIssue,\n type FieldMap,\n type Singleton,\n type Site,\n} from \"@micacms/core\";\n\nimport { ConflictError, FilesError, type ContentFiles, type FileChange } from \"../workspace/files.js\";\n\nexport class ContentError extends FilesError {\n override name = \"ContentError\";\n}\n\n/** A write to a collection or singleton that this build of Mica cannot interpret. */\nexport class ReadOnlyError extends ContentError {\n override name = \"ReadOnlyError\";\n}\n\nexport class ValidationError extends ContentError {\n override name = \"ValidationError\";\n constructor(\n message: string,\n readonly issues: FieldIssue[],\n ) {\n super(message);\n }\n}\n\nexport interface SaveEntryInput {\n data: Record<string, unknown>;\n /** The entry being replaced. Absent when creating. */\n slug?: string;\n /** The revision `slug` had when the editor opened it. Absent writes unconditionally. */\n expectedRevision?: string | null;\n}\n\nexport interface SavedEntry {\n slug: string;\n title: string;\n /** The entry's revision after this save, for the next save. A file revision, not a commit. */\n revision: string;\n /** True when the save moved the entry's files. */\n renamed: boolean;\n}\n\n/** `readOnly` comes from `readOnlyParts`: its collections and singletons are read, never written. */\nexport function createContentStore(site: Site, workspace: ContentFiles, readOnly: ReadonlyMap<string, string> = new Map()) {\n const requireCollection = (name: string): Collection => {\n const def = site.collections[name];\n if (!def) throw new ContentError(`Unknown collection \"${name}\".`);\n return def;\n };\n\n const requireSingleton = (name: string): Singleton => {\n const def = site.singletons[name];\n if (!def) throw new ContentError(`Unknown singleton \"${name}\".`);\n return def;\n };\n\n const writable = <T>(def: T, key: string): T => {\n const reason = readOnly.get(key);\n if (reason) throw new ReadOnlyError(reason);\n return def;\n };\n\n const validate = (\n def: Collection | Singleton,\n data: unknown,\n what: string,\n ): Record<string, unknown> => {\n const result = validateFields(def.fields, data, \"storage\");\n if (!result.ok) {\n throw new ValidationError(\n `${what} cannot be saved until these fields are correct.`,\n result.issues,\n );\n }\n return result.data as Record<string, unknown>;\n };\n\n /**\n * Deletes for `.mdoc` files not in `keep`, so a cleared rich-text field does\n * not come back on the next read. Media stays: an unreferenced upload is not\n * garbage.\n */\n const staleDocuments = async (base: string, keep: Set<string>): Promise<FileChange[]> => {\n const changes: FileChange[] = [];\n\n for (const item of await workspace.list(base)) {\n if (item.type !== \"file\") continue;\n if (!item.name.endsWith(DOCUMENT_EXTENSION)) continue;\n if (keep.has(item.name)) continue;\n changes.push({ type: \"delete\", path: `${base}/${item.name}` });\n }\n\n return changes;\n };\n\n /**\n * The document's revision after a write. Throws when the file is absent,\n * because an empty token could never match.\n */\n const revisionAfterWrite = async (dataFile: string, folder: string | null): Promise<string> => {\n const revision = await documentRevision(dataFile, folder);\n if (revision === null) {\n throw new ContentError(`Saved, but \"${dataFile}\" is not in the repository afterwards.`);\n }\n return revision;\n };\n\n /** The folder whose files make up one document, or null for a one-file document. */\n const folderOf = (fields: FieldMap, base: string): string | null =>\n entryLayout(fields) === \"directory\" ? base : null;\n\n /**\n * The revision of a whole document: its data file and, in the folder layout,\n * every file of its folder. A change to any one of them makes the next save a\n * conflict, not an overwrite.\n */\n const documentRevision = async (dataFile: string, folder: string | null): Promise<string | null> => {\n const files = await documentFiles(dataFile, folder);\n if (files === null) return null;\n if (folder === null) return files.get(dataFile)!;\n const parts = [...files].map(([path, revision]) => `${path}\\u0000${revision}`);\n return createHash(\"sha256\").update(parts.sort().join(\"\\n\")).digest(\"hex\").slice(0, 16);\n };\n\n /** Every file of a document with its revision, or null when its data file is absent. */\n const documentFiles = async (dataFile: string, folder: string | null): Promise<Map<string, string> | null> => {\n const data = await workspace.revision(dataFile);\n if (data === null) return null;\n const files = new Map([[dataFile, data]]);\n if (folder === null) return files;\n const walk = async (dir: string): Promise<void> => {\n for (const item of await workspace.list(dir)) {\n const child = `${dir}/${item.name}`;\n if (item.type === \"directory\") await walk(child);\n else files.set(child, (await workspace.revision(child)) ?? \"\");\n }\n };\n await walk(folder);\n return files;\n };\n\n /**\n * Refuses when the document changed since the editor read `expected`, and\n * returns every file's revision for `apply`, which checks them all again\n * under the write lock.\n */\n const expectDocument = async (\n expected: string | null | undefined,\n dataFile: string,\n folder: string | null,\n ): Promise<Record<string, string | null> | undefined> => {\n if (expected === undefined) return undefined;\n const files = await documentFiles(dataFile, folder);\n if ((await documentRevision(dataFile, folder)) !== expected) {\n throw new ConflictError(\"This content changed after you opened it. Reload to see the current version.\", [\n dataFile,\n ]);\n }\n return files === null ? { [dataFile]: null } : Object.fromEntries(files);\n };\n\n /**\n * The order file with a renamed slug in its old place, so the entry does not\n * drop to the end of the list.\n */\n const renameInOrder = async (name: string, from: string, to: string): Promise<FileChange | null> => {\n const path = `${name}/${ORDER_FILE}`;\n const raw = await workspace.read(path);\n if (raw === null) return null;\n\n let listed: unknown;\n try {\n listed = JSON.parse(raw);\n } catch {\n // Reading the collection reports a malformed order file; a save need not.\n return null;\n }\n if (!Array.isArray(listed) || !listed.includes(from)) return null;\n\n const order = listed.map((slug: unknown) => (slug === from ? to : slug));\n return { type: \"write\", path, contents: `${JSON.stringify(order, null, 2)}\\n` };\n };\n\n /** Slugs already taken in a collection, so a new one can be made unique. */\n const takenSlugs = async (def: Collection, name: string, except?: string): Promise<string[]> => {\n const entries = await readCollection(workspace, def, name);\n return entries.map((entry) => entry.slug).filter((slug) => slug !== except);\n };\n\n return {\n async list(collectionName: string): Promise<ContentEntry[]> {\n return readCollection(workspace, requireCollection(collectionName), collectionName);\n },\n\n async get(collectionName: string, slug: string): Promise<ContentEntry | null> {\n const entries = await this.list(collectionName);\n return entries.find((entry) => entry.slug === slug) ?? null;\n },\n\n /** The revision an editor must send back with a save of this entry. */\n async revisionOf(collectionName: string, slug: string): Promise<string | null> {\n const def = requireCollection(collectionName);\n return documentRevision(\n dataPath(def, collectionName, slug),\n folderOf(def.fields, collectionEntryPath(def.fields, collectionName, slug)),\n );\n },\n\n /** The revision an editor must send back with a save of this singleton. */\n async revisionOfSingleton(name: string): Promise<string | null> {\n const def = requireSingleton(name);\n return documentRevision(singletonDataPath(def, name), folderOf(def.fields, singletonPath(def.fields, name)));\n },\n\n async getSingleton(name: string): Promise<Record<string, unknown>> {\n const def = requireSingleton(name);\n const data = await readSingleton(workspace, def, name);\n // A singleton with no file yet opens as an empty form, not a 404.\n return withDefaults(def.fields, data);\n },\n\n async save(collectionName: string, input: SaveEntryInput): Promise<SavedEntry> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n const data = validate(def, input.data, \"This entry\");\n\n const slugKey = resolveSlugField(def)!;\n const requested = pickSlug(def, data, input.slug);\n const slug =\n requested === input.slug\n ? requested\n : uniqueSlug(requested, await takenSlugs(def, collectionName, input.slug));\n\n data[slugKey] = slug;\n\n const base = collectionEntryPath(def.fields, collectionName, slug);\n const previousBase =\n input.slug && input.slug !== slug\n ? collectionEntryPath(def.fields, collectionName, input.slug)\n : null;\n\n // The editor uses root-relative media paths; store them relative to the\n // entry (the old directory on a rename).\n const serialized = serializeEntry(\n def.fields,\n mediaForDisk(def.fields, data, previousBase ?? base),\n );\n const files = entryFiles(def.fields, base, serialized);\n\n const changes: FileChange[] = Object.entries(files).map(([path, contents]) => ({\n type: \"write\",\n path,\n contents,\n }));\n\n if (previousBase) {\n changes.push(\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: previousBase }\n : { type: \"delete\", path: previousBase },\n );\n await moveMedia(workspace, previousBase, base, changes);\n const order = await renameInOrder(collectionName, input.slug!, slug);\n if (order) changes.push(order);\n } else if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n const title = entryTitle(def, data);\n await workspace.apply({\n changes,\n expect: await expectDocument(\n input.expectedRevision,\n dataPath(def, collectionName, input.slug ?? slug),\n folderOf(def.fields, previousBase ?? base),\n ),\n });\n\n const revision = await revisionAfterWrite(dataPath(def, collectionName, slug), folderOf(def.fields, base));\n\n return { slug, title, revision, renamed: previousBase !== null };\n },\n\n async remove(\n collectionName: string,\n slug: string,\n options: { expectedRevision?: string | null } = {},\n ): Promise<void> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n // One path segment: an empty slug or `a/b` would delete more than one entry.\n if (slug === \"\" || slug.includes(\"/\") || slug === \".\" || slug === \"..\") {\n throw new ContentPathError(`\"${slug}\" is not an entry of \"${collectionName}\".`);\n }\n const base = collectionEntryPath(def.fields, collectionName, slug);\n\n await workspace.apply({\n changes: [\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: base }\n : { type: \"delete\", path: base },\n ],\n expect: await expectDocument(\n options.expectedRevision,\n dataPath(def, collectionName, slug),\n folderOf(def.fields, base),\n ),\n });\n },\n\n async saveSingleton(\n name: string,\n payload: unknown,\n options: { expectedRevision?: string | null } = {},\n ): Promise<{ revision: string }> {\n const def = writable(requireSingleton(name), `singletons.${name}`);\n const data = validate(def, payload, `The \"${def.label}\" settings`);\n\n const base = singletonPath(def.fields, name);\n const serialized = serializeEntry(def.fields, mediaForDisk(def.fields, data, base));\n\n const changes: FileChange[] = Object.entries(entryFiles(def.fields, base, serialized)).map(\n ([path, contents]) => ({ type: \"write\", path, contents }),\n );\n\n if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n await workspace.apply({\n changes,\n expect: await expectDocument(options.expectedRevision, singletonDataPath(def, name), folderOf(def.fields, base)),\n });\n\n return { revision: await revisionAfterWrite(singletonDataPath(def, name), folderOf(def.fields, base)) };\n },\n\n async reorder(\n collectionName: string,\n slugs: string[],\n ): Promise<void> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n if (!def.sortable) {\n throw new ContentError(`The \"${def.label}\" collection is not manually sortable.`);\n }\n\n await workspace.apply({\n changes: [\n {\n type: \"write\",\n path: `${collectionName}/${ORDER_FILE}`,\n contents: `${JSON.stringify(slugs, null, 2)}\\n`,\n },\n ],\n });\n },\n\n requireCollection,\n requireSingleton,\n\n /** Slug the server would assign, so the editor's URL preview matches it. */\n async suggestSlug(collectionName: string, source: string, except?: string): Promise<string> {\n const def = requireCollection(collectionName);\n return uniqueSlug(slugify(source), await takenSlugs(def, collectionName, except));\n },\n\n /** Entries pointing at `slug`, so the editor can warn before a delete. */\n async findReferences(collectionName: string, slug: string) {\n const results: Array<{ collection: string; slug: string; title: string }> = [];\n\n for (const [name, def] of Object.entries(site.collections)) {\n const keys = Object.entries(def.fields)\n .filter(([, field]) => field.kind === \"relation\" && field.collection === collectionName)\n .map(([key]) => key);\n\n if (keys.length === 0) continue;\n\n for (const entry of await readCollection(workspace, def, name)) {\n const points = keys.some((key) => {\n const value = entry.data[key];\n return Array.isArray(value) ? value.includes(slug) : value === slug;\n });\n if (points) {\n results.push({ collection: name, slug: entry.slug, title: entryTitle(def, entry.data) });\n }\n }\n }\n\n return results;\n },\n };\n}\n\nexport type ContentStore = ReturnType<typeof createContentStore>;\n\n/**\n * Media paths as the repository stores them. Only a directory entry has\n * colocated media to relativize.\n */\nfunction mediaForDisk(\n fields: FieldMap,\n data: Record<string, unknown>,\n base: string,\n): Record<string, unknown> {\n return entryLayout(fields) === \"directory\" ? relativizeMedia(fields, data, base) : data;\n}\n\nfunction dataPath(def: Collection, collection: string, slug: string): string {\n const base = collectionEntryPath(def.fields, collection, slug);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\nfunction singletonDataPath(def: Singleton, name: string): string {\n const base = singletonPath(def.fields, name);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\n/** The slug from the payload's slug field, or one derived from the title. */\nfunction pickSlug(\n def: Collection,\n data: Record<string, unknown>,\n current: string | undefined,\n): string {\n const slugKey = resolveSlugField(def)!;\n const fromData = data[slugKey];\n\n const candidate =\n (typeof fromData === \"string\" && fromData.trim()) || slugify(entryTitle(def, data));\n\n if (!candidate) {\n throw new ContentError(\"An entry needs a title or a URL before it can be saved.\");\n }\n\n const normalised = isValidSlug(candidate) ? candidate : slugify(candidate);\n if (!normalised) {\n throw new ContentError(`\"${candidate}\" cannot be turned into a URL. Use letters and numbers.`);\n }\n\n return normalised === current ? current : normalised;\n}\n\n/** Copies colocated media to the renamed entry; the caller deletes the old directory. */\nasync function moveMedia(\n files: ContentFiles,\n from: string,\n to: string,\n changes: FileChange[],\n): Promise<void> {\n for (const item of await files.list(`${from}/media`)) {\n if (item.type !== \"file\") continue;\n\n const bytes = await files.readBinary(`${from}/media/${item.name}`);\n if (bytes === null) continue;\n\n changes.push({ type: \"write\", path: `${to}/media/${item.name}`, contents: bytes });\n }\n}\n"]}
|
|
@@ -25,6 +25,8 @@ export interface WorkingTreeStagingDeps {
|
|
|
25
25
|
files: ContentFiles;
|
|
26
26
|
contentRoot: string;
|
|
27
27
|
schemaRevision: string | null;
|
|
28
|
+
/** From `readOnlyParts`: a change to one of these is never published by this build. */
|
|
29
|
+
readOnly?: ReadonlyMap<string, string>;
|
|
28
30
|
/** `MICA_LOCAL_PUBLISH`, for the default developer policy. */
|
|
29
31
|
publishEnabled: boolean;
|
|
30
32
|
logger: Logger;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAmD,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAG3F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EASL,KAAK,OAAO,EAEb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAQL,KAAK,qBAAqB,EAK3B,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,IAAI,CAAC;IACZ;;;OAGG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,8DAA8D;IAC9D,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mDAAmD;IACnD,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,sBAAsB,GAC3B,qBAAqB,
|
|
1
|
+
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAmD,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAG3F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EASL,KAAK,OAAO,EAEb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAQL,KAAK,qBAAqB,EAK3B,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,IAAI,CAAC;IACZ;;;OAGG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,uFAAuF;IACvF,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,8DAA8D;IAC9D,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mDAAmD;IACnD,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,sBAAsB,GAC3B,qBAAqB,CAsPvB"}
|
package/dist/services/staging.js
CHANGED
|
@@ -34,6 +34,14 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
34
34
|
],
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
+
// By the first path segment, so that a stray file in a read-only collection is caught too.
|
|
38
|
+
for (const change of changes) {
|
|
39
|
+
const head = change.path.split("/")[0] ?? "";
|
|
40
|
+
const reason = deps.readOnly?.get(`collections.${head}`) ?? deps.readOnly?.get(`singletons.${head.replace(/\.json$/, "")}`);
|
|
41
|
+
if (reason) {
|
|
42
|
+
validation.problems.push({ path: change.path, label: change.owner.label, issues: [{ path: "", message: reason }] });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
37
45
|
validation.ok = validation.problems.length === 0;
|
|
38
46
|
return { changes, validation };
|
|
39
47
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staging.js","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The staging service on a Git working tree. Autosave has already written the\n * files; Publish commits the reviewed paths.\n *\n * - Every Git operation is limited to an explicit path set, so other work in\n * the same checkout is never swept in.\n * - Only the reviewed files are published, as the editor saw them. Drafts of\n * other entries stay drafts.\n * - When the push fails, the commit stays as `committed-pending-push`; a\n * retry pushes and never commits again.\n */\n\nimport { MANIFEST_PATH, memorySource, type PublishResult, type Site } from \"@micacms/core\";\n\nimport { ApiError, badRequest, notFound } from \"../lib/errors.js\";\nimport type { Logger } from \"../lib/logger.js\";\nimport { developerPolicy } from \"../workspace/developer.js\";\nimport type { ContentFiles } from \"../workspace/files.js\";\nimport { GitError } from \"../workspace/git.js\";\nimport { createWorkspaceLocks, type WorkspaceLocks } from \"../workspace/locks.js\";\nimport type { OwnerPolicy } from \"../workspace/policy.js\";\nimport {\n aheadOfUpstream,\n commitPaths,\n currentBranch,\n discardChange,\n headRevision,\n headSource,\n listTreeChanges,\n locateTree,\n type GitTree,\n type TreeChange,\n} from \"../workspace/tree.js\";\nimport {\n analyseChanges,\n compareSources,\n describeProblems,\n flattenProblems,\n type AnnotatedChange,\n type CompareTarget,\n type Comparison,\n type ContentStagingService,\n type PublishInput,\n type RepositoryInfo,\n type StagingStatus,\n type StagingValidation,\n} from \"./changes.js\";\n\nexport interface WorkingTreeStagingDeps {\n model: Site;\n /**\n * The working tree's files without the lock wrapper: this service holds the\n * locks around whole operations.\n */\n files: ContentFiles;\n contentRoot: string;\n schemaRevision: string | null;\n /** `MICA_LOCAL_PUBLISH`, for the default developer policy. */\n publishEnabled: boolean;\n logger: Logger;\n /** Defaults to the developer. */\n policy?: OwnerPolicy;\n /** Shared with the services that write content. */\n locks?: WorkspaceLocks;\n /**\n * Set by a managed clone, whose Git runs in an isolated environment. Without\n * it, the developer's Git finds the tree.\n */\n tree?: GitTree;\n}\n\nexport function createWorkingTreeStagingService(\n deps: WorkingTreeStagingDeps,\n): ContentStagingService {\n const { model, files, contentRoot, logger } = deps;\n const policy = deps.policy ?? developerPolicy({ publishEnabled: deps.publishEnabled, logger });\n const locks = deps.locks ?? createWorkspaceLocks();\n\n const analyse = async (tree: GitTree, treeChanges: TreeChange[]) => {\n const { changes, validation } = await analyseChanges(\n model,\n files,\n treeChanges,\n headSource(tree),\n );\n\n // The field validator cannot see a merge conflict: the file holds Git's conflict format.\n for (const change of treeChanges) {\n if (!change.conflicted) continue;\n validation.problems.push({\n path: change.path,\n label: change.path,\n issues: [\n { path: \"\", message: \"This file has a Git merge conflict. Resolve it with your Git tools.\" },\n ],\n });\n }\n validation.ok = validation.problems.length === 0;\n\n return { changes, validation };\n };\n\n const locate = async (): Promise<GitTree | { reason: string }> => deps.tree ?? locateTree(contentRoot);\n\n const located = async (): Promise<GitTree> => {\n const tree = await locate();\n if (\"reason\" in tree) throw badRequest(tree.reason);\n return tree;\n };\n\n const publishable = async (): Promise<GitTree> => {\n const availability = policy.publishAvailability();\n if (!availability.enabled) throw badRequest(availability.reason);\n return located();\n };\n\n /** The key that keeps an entry's files together: a publication never splits one. */\n const groupOf = (change: AnnotatedChange): string => {\n const owner = change.owner;\n if (owner.kind === \"entry\") return `entry:${owner.collection}/${owner.slug}`;\n if (owner.kind === \"singleton\") return `singleton:${owner.name}`;\n if (owner.kind === \"order\") return `order:${owner.collection}`;\n return `file:${change.path}`;\n };\n\n const withRevisions = async (changes: AnnotatedChange[]): Promise<AnnotatedChange[]> =>\n Promise.all(\n changes.map(async (change) => ({\n ...change,\n revision: change.kind === \"delete\" ? null : await files.revision(change.path),\n })),\n );\n\n /** Refused for the developer's checkout: Mica never fetches or merges there. */\n const managedOnly = <T>(action: T | undefined): T => {\n if (action === undefined) throw notFound(\"This is the developer's checkout; Git there is theirs.\");\n return action;\n };\n\n return {\n async status(): Promise<StagingStatus> {\n const tree = await locate();\n const sync = policy.syncStatus?.() ?? null;\n\n if (\"reason\" in tree) {\n return {\n owner: policy.owner,\n publish: { enabled: false, reason: tree.reason },\n head: null,\n branch: null,\n ahead: null,\n changes: [],\n validation: emptyValidation(),\n sync,\n };\n }\n\n const { changes, validation, head, branch } = await locks.tree.read(async () => {\n const analysed = await analyse(tree, await listTreeChanges(tree));\n return {\n ...analysed,\n changes: await withRevisions(analysed.changes),\n head: await headRevision(tree),\n branch: await currentBranch(tree),\n };\n });\n\n return {\n owner: policy.owner,\n publish: policy.publishAvailability(),\n head,\n branch,\n ahead: await aheadOfUpstream(tree),\n changes,\n validation,\n sync,\n };\n },\n\n async publish(input: PublishInput): Promise<PublishResult> {\n const message = input.message.trim();\n if (!message) throw badRequest(\"A publication needs a commit message.\");\n const reviewed = Object.keys(input.paths);\n if (reviewed.length === 0) throw badRequest(\"Choose at least one change to publish.\");\n\n const tree = await publishable();\n\n // One publication at a time from sync to push: no sync can run in\n // between, and no second commit can stack up.\n return locks.remote.run(async () => {\n await policy.prepare(tree);\n\n const revision = await locks.tree.write(async () => {\n const pending = await withRevisions((await analyse(tree, await listTreeChanges(tree))).changes);\n const byPath = new Map(pending.map((change) => [change.path, change]));\n\n // The review gate refuses a file saved again since the review, a\n // reviewed file that is no longer pending, or another file of a\n // reviewed entry. Drafts of other entries never block a publication.\n const reviewedGroups = new Set(\n reviewed.map((path) => byPath.get(path)).flatMap((change) => (change ? [groupOf(change)] : [])),\n );\n const stale = [\n ...reviewed.filter((path) => byPath.get(path)?.revision !== input.paths[path] || !byPath.has(path)),\n ...pending\n .filter((change) => reviewedGroups.has(groupOf(change)) && !(change.path in input.paths))\n .map((change) => change.path),\n ];\n if (stale.length > 0) {\n throw new ApiError(\n \"conflict\",\n \"Some of these changes were edited after you reviewed them. Review them again, then publish.\",\n );\n }\n\n // The model drift gate: validation used the model that this process started with.\n if ((await files.revision(MANIFEST_PATH)) !== deps.schemaRevision) {\n throw new ApiError(\n \"conflict\",\n \"The content model changed after this server started. Restart Mica to load \" +\n \"the new model, then review the pending changes and publish again.\",\n );\n }\n\n const selected = (await listTreeChanges(tree)).filter((change) => change.path in input.paths);\n const { validation } = await analyse(tree, selected);\n if (!validation.ok) {\n throw new ApiError(\"validation_failed\", describeProblems(validation.problems), {\n issues: flattenProblems(validation.problems),\n });\n }\n\n try {\n const revision = await commitPaths(tree, reviewed, message, policy.commitAuthor(input.author));\n logger.info(\"Published\", { revision, files: reviewed.length, owner: policy.owner });\n return revision;\n } catch (error) {\n if (error instanceof GitError) {\n // Git's message is the actionable part: no identity, a hook that\n // refused, a rebase in progress.\n throw new ApiError(\"conflict\", `Git refused the commit: ${error.message}`);\n }\n throw error;\n }\n });\n\n return policy.push(tree, revision);\n });\n },\n\n async push(): Promise<PublishResult> {\n const tree = await publishable();\n\n return locks.remote.run(async () => {\n const revision = await headRevision(tree);\n if (revision === null) throw badRequest(\"There is no commit to push yet.\");\n return policy.push(tree, revision);\n });\n },\n\n async discard(requested: string[]): Promise<{ discarded: string[] }> {\n const tree = await located();\n\n return locks.tree.write(async () => {\n // Discard is destructive, so it reaches only paths that the change list names.\n const current = new Map((await listTreeChanges(tree)).map((change) => [change.path, change]));\n\n const discarded: string[] = [];\n for (const path of requested) {\n const change = current.get(path);\n if (!change) continue;\n await discardChange(tree, change);\n discarded.push(path);\n }\n\n return { discarded };\n });\n },\n\n async compare(target: CompareTarget, against: \"head\" | \"origin\" = \"head\"): Promise<Comparison> {\n const tree = await locate();\n if (against === \"origin\" && !policy.upstreamRef) throw badRequest(\"There is no website version to compare with here.\");\n // Without a repository nothing is published, so \"before\" is an empty source.\n const published =\n \"reason\" in tree\n ? memorySource({}, \"unpublished\")\n : headSource(tree, against === \"origin\" ? policy.upstreamRef : \"HEAD\");\n return locks.tree.read(() => compareSources(model, published, files, target));\n },\n\n async sync(): Promise<void> {\n await managedOnly(policy.sync)();\n },\n\n async resolve(group: string, choice: \"mine\" | \"website\"): Promise<void> {\n await managedOnly(policy.resolve)(group, choice);\n },\n\n async repository(): Promise<RepositoryInfo> {\n const tree = await locate();\n const described = (await policy.describe?.()) ?? null;\n return {\n owner: policy.owner,\n branch: \"reason\" in tree ? null : await currentBranch(tree),\n head: \"reason\" in tree ? null : await headRevision(tree),\n url: described?.url ?? null,\n cloneBytes: described?.cloneBytes ?? null,\n sync: policy.syncStatus?.() ?? null,\n };\n },\n };\n}\n\nfunction emptyValidation(): StagingValidation {\n return { ok: true, problems: [] };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"staging.js","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The staging service on a Git working tree. Autosave has already written the\n * files; Publish commits the reviewed paths.\n *\n * - Every Git operation is limited to an explicit path set, so other work in\n * the same checkout is never swept in.\n * - Only the reviewed files are published, as the editor saw them. Drafts of\n * other entries stay drafts.\n * - When the push fails, the commit stays as `committed-pending-push`; a\n * retry pushes and never commits again.\n */\n\nimport { MANIFEST_PATH, memorySource, type PublishResult, type Site } from \"@micacms/core\";\n\nimport { ApiError, badRequest, notFound } from \"../lib/errors.js\";\nimport type { Logger } from \"../lib/logger.js\";\nimport { developerPolicy } from \"../workspace/developer.js\";\nimport type { ContentFiles } from \"../workspace/files.js\";\nimport { GitError } from \"../workspace/git.js\";\nimport { createWorkspaceLocks, type WorkspaceLocks } from \"../workspace/locks.js\";\nimport type { OwnerPolicy } from \"../workspace/policy.js\";\nimport {\n aheadOfUpstream,\n commitPaths,\n currentBranch,\n discardChange,\n headRevision,\n headSource,\n listTreeChanges,\n locateTree,\n type GitTree,\n type TreeChange,\n} from \"../workspace/tree.js\";\nimport {\n analyseChanges,\n compareSources,\n describeProblems,\n flattenProblems,\n type AnnotatedChange,\n type CompareTarget,\n type Comparison,\n type ContentStagingService,\n type PublishInput,\n type RepositoryInfo,\n type StagingStatus,\n type StagingValidation,\n} from \"./changes.js\";\n\nexport interface WorkingTreeStagingDeps {\n model: Site;\n /**\n * The working tree's files without the lock wrapper: this service holds the\n * locks around whole operations.\n */\n files: ContentFiles;\n contentRoot: string;\n schemaRevision: string | null;\n /** From `readOnlyParts`: a change to one of these is never published by this build. */\n readOnly?: ReadonlyMap<string, string>;\n /** `MICA_LOCAL_PUBLISH`, for the default developer policy. */\n publishEnabled: boolean;\n logger: Logger;\n /** Defaults to the developer. */\n policy?: OwnerPolicy;\n /** Shared with the services that write content. */\n locks?: WorkspaceLocks;\n /**\n * Set by a managed clone, whose Git runs in an isolated environment. Without\n * it, the developer's Git finds the tree.\n */\n tree?: GitTree;\n}\n\nexport function createWorkingTreeStagingService(\n deps: WorkingTreeStagingDeps,\n): ContentStagingService {\n const { model, files, contentRoot, logger } = deps;\n const policy = deps.policy ?? developerPolicy({ publishEnabled: deps.publishEnabled, logger });\n const locks = deps.locks ?? createWorkspaceLocks();\n\n const analyse = async (tree: GitTree, treeChanges: TreeChange[]) => {\n const { changes, validation } = await analyseChanges(\n model,\n files,\n treeChanges,\n headSource(tree),\n );\n\n // The field validator cannot see a merge conflict: the file holds Git's conflict format.\n for (const change of treeChanges) {\n if (!change.conflicted) continue;\n validation.problems.push({\n path: change.path,\n label: change.path,\n issues: [\n { path: \"\", message: \"This file has a Git merge conflict. Resolve it with your Git tools.\" },\n ],\n });\n }\n // By the first path segment, so that a stray file in a read-only collection is caught too.\n for (const change of changes) {\n const head = change.path.split(\"/\")[0] ?? \"\";\n const reason = deps.readOnly?.get(`collections.${head}`) ?? deps.readOnly?.get(`singletons.${head.replace(/\\.json$/, \"\")}`);\n if (reason) {\n validation.problems.push({ path: change.path, label: change.owner.label, issues: [{ path: \"\", message: reason }] });\n }\n }\n validation.ok = validation.problems.length === 0;\n\n return { changes, validation };\n };\n\n const locate = async (): Promise<GitTree | { reason: string }> => deps.tree ?? locateTree(contentRoot);\n\n const located = async (): Promise<GitTree> => {\n const tree = await locate();\n if (\"reason\" in tree) throw badRequest(tree.reason);\n return tree;\n };\n\n const publishable = async (): Promise<GitTree> => {\n const availability = policy.publishAvailability();\n if (!availability.enabled) throw badRequest(availability.reason);\n return located();\n };\n\n /** The key that keeps an entry's files together: a publication never splits one. */\n const groupOf = (change: AnnotatedChange): string => {\n const owner = change.owner;\n if (owner.kind === \"entry\") return `entry:${owner.collection}/${owner.slug}`;\n if (owner.kind === \"singleton\") return `singleton:${owner.name}`;\n if (owner.kind === \"order\") return `order:${owner.collection}`;\n return `file:${change.path}`;\n };\n\n const withRevisions = async (changes: AnnotatedChange[]): Promise<AnnotatedChange[]> =>\n Promise.all(\n changes.map(async (change) => ({\n ...change,\n revision: change.kind === \"delete\" ? null : await files.revision(change.path),\n })),\n );\n\n /** Refused for the developer's checkout: Mica never fetches or merges there. */\n const managedOnly = <T>(action: T | undefined): T => {\n if (action === undefined) throw notFound(\"This is the developer's checkout; Git there is theirs.\");\n return action;\n };\n\n return {\n async status(): Promise<StagingStatus> {\n const tree = await locate();\n const sync = policy.syncStatus?.() ?? null;\n\n if (\"reason\" in tree) {\n return {\n owner: policy.owner,\n publish: { enabled: false, reason: tree.reason },\n head: null,\n branch: null,\n ahead: null,\n changes: [],\n validation: emptyValidation(),\n sync,\n };\n }\n\n const { changes, validation, head, branch } = await locks.tree.read(async () => {\n const analysed = await analyse(tree, await listTreeChanges(tree));\n return {\n ...analysed,\n changes: await withRevisions(analysed.changes),\n head: await headRevision(tree),\n branch: await currentBranch(tree),\n };\n });\n\n return {\n owner: policy.owner,\n publish: policy.publishAvailability(),\n head,\n branch,\n ahead: await aheadOfUpstream(tree),\n changes,\n validation,\n sync,\n };\n },\n\n async publish(input: PublishInput): Promise<PublishResult> {\n const message = input.message.trim();\n if (!message) throw badRequest(\"A publication needs a commit message.\");\n const reviewed = Object.keys(input.paths);\n if (reviewed.length === 0) throw badRequest(\"Choose at least one change to publish.\");\n\n const tree = await publishable();\n\n // One publication at a time from sync to push: no sync can run in\n // between, and no second commit can stack up.\n return locks.remote.run(async () => {\n await policy.prepare(tree);\n\n const revision = await locks.tree.write(async () => {\n const pending = await withRevisions((await analyse(tree, await listTreeChanges(tree))).changes);\n const byPath = new Map(pending.map((change) => [change.path, change]));\n\n // The review gate refuses a file saved again since the review, a\n // reviewed file that is no longer pending, or another file of a\n // reviewed entry. Drafts of other entries never block a publication.\n const reviewedGroups = new Set(\n reviewed.map((path) => byPath.get(path)).flatMap((change) => (change ? [groupOf(change)] : [])),\n );\n const stale = [\n ...reviewed.filter((path) => byPath.get(path)?.revision !== input.paths[path] || !byPath.has(path)),\n ...pending\n .filter((change) => reviewedGroups.has(groupOf(change)) && !(change.path in input.paths))\n .map((change) => change.path),\n ];\n if (stale.length > 0) {\n throw new ApiError(\n \"conflict\",\n \"Some of these changes were edited after you reviewed them. Review them again, then publish.\",\n );\n }\n\n // The model drift gate: validation used the model that this process started with.\n if ((await files.revision(MANIFEST_PATH)) !== deps.schemaRevision) {\n throw new ApiError(\n \"conflict\",\n \"The content model changed after this server started. Restart Mica to load \" +\n \"the new model, then review the pending changes and publish again.\",\n );\n }\n\n const selected = (await listTreeChanges(tree)).filter((change) => change.path in input.paths);\n const { validation } = await analyse(tree, selected);\n if (!validation.ok) {\n throw new ApiError(\"validation_failed\", describeProblems(validation.problems), {\n issues: flattenProblems(validation.problems),\n });\n }\n\n try {\n const revision = await commitPaths(tree, reviewed, message, policy.commitAuthor(input.author));\n logger.info(\"Published\", { revision, files: reviewed.length, owner: policy.owner });\n return revision;\n } catch (error) {\n if (error instanceof GitError) {\n // Git's message is the actionable part: no identity, a hook that\n // refused, a rebase in progress.\n throw new ApiError(\"conflict\", `Git refused the commit: ${error.message}`);\n }\n throw error;\n }\n });\n\n return policy.push(tree, revision);\n });\n },\n\n async push(): Promise<PublishResult> {\n const tree = await publishable();\n\n return locks.remote.run(async () => {\n const revision = await headRevision(tree);\n if (revision === null) throw badRequest(\"There is no commit to push yet.\");\n return policy.push(tree, revision);\n });\n },\n\n async discard(requested: string[]): Promise<{ discarded: string[] }> {\n const tree = await located();\n\n return locks.tree.write(async () => {\n // Discard is destructive, so it reaches only paths that the change list names.\n const current = new Map((await listTreeChanges(tree)).map((change) => [change.path, change]));\n\n const discarded: string[] = [];\n for (const path of requested) {\n const change = current.get(path);\n if (!change) continue;\n await discardChange(tree, change);\n discarded.push(path);\n }\n\n return { discarded };\n });\n },\n\n async compare(target: CompareTarget, against: \"head\" | \"origin\" = \"head\"): Promise<Comparison> {\n const tree = await locate();\n if (against === \"origin\" && !policy.upstreamRef) throw badRequest(\"There is no website version to compare with here.\");\n // Without a repository nothing is published, so \"before\" is an empty source.\n const published =\n \"reason\" in tree\n ? memorySource({}, \"unpublished\")\n : headSource(tree, against === \"origin\" ? policy.upstreamRef : \"HEAD\");\n return locks.tree.read(() => compareSources(model, published, files, target));\n },\n\n async sync(): Promise<void> {\n await managedOnly(policy.sync)();\n },\n\n async resolve(group: string, choice: \"mine\" | \"website\"): Promise<void> {\n await managedOnly(policy.resolve)(group, choice);\n },\n\n async repository(): Promise<RepositoryInfo> {\n const tree = await locate();\n const described = (await policy.describe?.()) ?? null;\n return {\n owner: policy.owner,\n branch: \"reason\" in tree ? null : await currentBranch(tree),\n head: \"reason\" in tree ? null : await headRevision(tree),\n url: described?.url ?? null,\n cloneBytes: described?.cloneBytes ?? null,\n sync: policy.syncStatus?.() ?? null,\n };\n },\n };\n}\n\nfunction emptyValidation(): StagingValidation {\n return { ok: true, problems: [] };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@micacms/runtime",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Mica single-site runtime: the Hono API, application services and staging orchestration, authentication-neutral. Composed by @micacms/editor locally and @micacms/self-hosted behind OIDC.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"hono": "^4.13.7",
|
|
30
30
|
"image-size": "^2.0.3",
|
|
31
31
|
"zod": "^4.3.6",
|
|
32
|
-
"@micacms/core": "0.5.0
|
|
32
|
+
"@micacms/core": "0.5.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^22.20.1",
|