@phtngyn/cms 0.1.0-beta.5 → 0.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +114 -41
- package/package.json +13 -1
- package/src/ApplicationTypes.d.ts +84 -0
- package/src/ApplicationTypes.js +1 -0
- package/src/{Configuration-CDMYfTre.js → Configuration-BU-UyGoH.js} +4 -4
- package/src/{Configuration-CDMYfTre.js.map → Configuration-BU-UyGoH.js.map} +1 -1
- package/src/Configuration.d.ts +43 -21
- package/src/Configuration.js.map +1 -1
- package/src/Content.d.ts +67 -0
- package/src/Content.js +291 -0
- package/src/Content.js.map +1 -0
- package/src/{ContentTransfer-C3Av9sWz.js → ContentTransfer-BhF9G56s.js} +8 -4
- package/src/ContentTransfer-BhF9G56s.js.map +1 -0
- package/src/ContentTransfer-CRQet3QJ.d.ts +48 -0
- package/src/{DocumentEngine-J7g-emdf.js → DocumentEngine-ClhFXW9M.js} +4 -4
- package/src/{DocumentEngine-J7g-emdf.js.map → DocumentEngine-ClhFXW9M.js.map} +1 -1
- package/src/DocumentModel.d.ts +14 -2
- package/src/DocumentModel.js +32 -4
- package/src/DocumentModel.js.map +1 -1
- package/src/{Forms-CfcI3tPU.js → Forms-BbkE7QfC.js} +47 -16
- package/src/Forms-BbkE7QfC.js.map +1 -0
- package/src/{Forms-Dj6NuW66.js → Forms-DoB56hfe.js} +24 -7
- package/src/Forms-DoB56hfe.js.map +1 -0
- package/src/{Forms-BsJMev5_.d.ts → Forms-R0HoTR-o.d.ts} +31 -13
- package/src/Forms.d.ts +23 -11
- package/src/Forms.js.map +1 -1
- package/src/I18n-zbqpWxki.d.ts +29 -0
- package/src/I18n.d.ts +2 -20
- package/src/Module.js +18 -7
- package/src/Module.js.map +1 -1
- package/src/{S3MediaObjects-CpPSzJto.js → S3MediaObjects-D5Ns4FM6.js} +61 -40
- package/src/S3MediaObjects-D5Ns4FM6.js.map +1 -0
- package/src/cli/content.js +63 -221
- package/src/cli/content.js.map +1 -1
- package/src/cli.js +120 -5
- package/src/cli.js.map +1 -1
- package/src/{connection-CBAy2qGy.js → connection-D5S9Iph-.js} +2 -2
- package/src/{connection-CBAy2qGy.js.map → connection-D5S9Iph-.js.map} +1 -1
- package/src/dev-lifecycle-CZ7iCzG9.js +515 -0
- package/src/dev-lifecycle-CZ7iCzG9.js.map +1 -0
- package/src/{installation-CNfIriVm.js → installation-BNuoRDOz.js} +601 -468
- package/src/installation-BNuoRDOz.js.map +1 -0
- package/src/internal/application-types.js +43 -0
- package/src/internal/application-types.js.map +1 -0
- package/src/internal/authoring-catalog.js +9 -4
- package/src/internal/authoring-catalog.js.map +1 -1
- package/src/internal/authoring-metadata.js +1 -1
- package/src/internal/configuration.js +149 -46
- package/src/internal/configuration.js.map +1 -1
- package/src/internal/dev-lifecycle.js +3 -0
- package/src/internal/diagnostics.js +47 -0
- package/src/internal/diagnostics.js.map +1 -0
- package/src/internal/document-model.js +2 -2
- package/src/internal/document-model.js.map +1 -1
- package/src/internal/form-capabilities.js +1 -1
- package/src/internal/i18n.js +27 -1
- package/src/internal/i18n.js.map +1 -1
- package/src/{location-BTqdfIzX.js → location-CPX7oj0o.js} +2 -2
- package/src/{location-BTqdfIzX.js.map → location-CPX7oj0o.js.map} +1 -1
- package/src/{models-B4IoTmp7.js → models-C9kF9JlQ.js} +2 -2
- package/src/{models-B4IoTmp7.js.map → models-C9kF9JlQ.js.map} +1 -1
- package/src/{models-Bah5B3bT.js → models-ChRxBChy.js} +9 -2
- package/src/models-ChRxBChy.js.map +1 -0
- package/src/runtime/delivery/context.js +1 -1
- package/src/runtime/delivery/plugin.js +28 -5
- package/src/runtime/delivery/plugin.js.map +1 -1
- package/src/runtime/delivery/preview.d.ts +1 -1
- package/src/runtime/delivery/types.d.ts +2 -85
- package/src/runtime/delivery/useCmsDocumentSeo.d.ts +8 -1
- package/src/runtime/delivery/useCmsDocumentSeo.js +19 -7
- package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -1
- package/src/runtime/delivery/useDocument.d.ts +2 -2
- package/src/runtime/delivery/useDocument.js.map +1 -1
- package/src/runtime/delivery/useDocumentQuery.d.ts +3 -3
- package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
- package/src/runtime/forms/document.js +1 -1
- package/src/runtime/forms/useCmsForm.js +89 -45
- package/src/runtime/forms/useCmsForm.js.map +1 -1
- package/src/runtime/i18n/useCmsI18n.d.ts +9 -7
- package/src/runtime/i18n/useCmsI18n.js +5 -4
- package/src/runtime/i18n/useCmsI18n.js.map +1 -1
- package/src/runtime/installation.js +2 -2
- package/src/runtime/plugin.js +12 -4
- package/src/runtime/plugin.js.map +1 -1
- package/src/runtime/preview/connection.js +1 -1
- package/src/runtime/preview/controller.js +2 -2
- package/src/runtime/preview/plugin.client.js +3 -3
- package/src/runtime/preview/store.js +1 -1
- package/src/runtime/sitemap.js +1 -1
- package/src/scalars-CA6kyipS.d.ts +7 -0
- package/src/{store-b2CSXgMt.js → store-B49J21NA.js} +3 -3
- package/src/{store-b2CSXgMt.js.map → store-B49J21NA.js.map} +1 -1
- package/src/types-DutFRtaG.d.ts +90 -0
- package/studio/{CreateDocumentDialog-GGey1uBc.js → CreateDocumentDialog-DJ8c0WZb.js} +3 -3
- package/studio/{documentPlanning-DRErYmBU.js → documentPlanning-CDb31mnl.js} +5935 -5869
- package/studio/host.css +1 -1
- package/studio/host.js +7608 -7250
- package/src/ContentTransfer-C3Av9sWz.js.map +0 -1
- package/src/Forms-CfcI3tPU.js.map +0 -1
- package/src/Forms-Dj6NuW66.js.map +0 -1
- package/src/S3MediaObjects-CpPSzJto.js.map +0 -1
- package/src/installation-CNfIriVm.js.map +0 -1
- package/src/models-Bah5B3bT.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-CBAy2qGy.js","names":["DocumentPreviewMessageSchema"],"sources":["../../../../protocol/src/internal/preview/bridge.ts","../../../src/runtime/preview/connection.ts"],"sourcesContent":["import type {\n DocumentPreviewMessage,\n PreviewOrigin,\n PreviewSessionId,\n PreviewSitePayload,\n StudioPreviewPayload,\n} from './models'\nimport type { Scope } from 'effect'\nimport { Effect, Result, Schedule, Schema } from 'effect'\nimport {\n DOCUMENT_PREVIEW_CHANNEL,\n DocumentPreviewMessage as DocumentPreviewMessageSchema,\n isDocumentPreviewMessage,\n} from './models'\n\nexport interface PreviewMessageEvent {\n readonly data: unknown\n readonly origin: string\n readonly source: object | null\n}\n\nexport interface PreviewMessageSource {\n readonly addEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n readonly removeEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n}\n\nexport interface PreviewMessageTarget {\n readonly postMessage: (message: DocumentPreviewMessage, targetOrigin: string) => void\n}\n\nexport type PreviewBridgeState = 'closed' | 'connecting' | 'ready'\n\nexport interface PreviewHostBridge {\n readonly reconnect: () => void\n readonly send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) => boolean\n readonly state: () => PreviewBridgeState\n readonly subscribe: (listener: (message: PreviewSitePayload) => void) => () => void\n}\n\nexport interface PreviewSiteBridge {\n readonly send: (payload: Exclude<PreviewSitePayload, { readonly type: 'preview:ready' }>) => boolean\n readonly subscribe: (listener: (message: StudioPreviewPayload) => void) => () => void\n}\n\nexport interface PreviewHostBridgeOptions {\n readonly handshakeIntervalMs?: number\n readonly sessionId: PreviewSessionId\n readonly target: () => PreviewMessageTarget | undefined\n readonly targetOrigin: PreviewOrigin\n readonly view: PreviewMessageSource\n}\n\nexport interface PreviewSiteBridgeOptions {\n readonly parent: PreviewMessageTarget\n readonly parentOrigin: PreviewOrigin\n readonly route: PreviewSitePayload & { readonly type: 'preview:ready' }\n readonly sessionId: PreviewSessionId\n readonly view: PreviewMessageSource\n}\n\nexport function makePreviewHostBridge(\n options: PreviewHostBridgeOptions,\n): Effect.Effect<PreviewHostBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.context<never>().pipe(\n Effect.map((context) => createPreviewHostBridge(options, Effect.runForkWith(context))),\n ),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nexport function makePreviewSiteBridge(\n options: PreviewSiteBridgeOptions,\n): Effect.Effect<PreviewSiteBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.sync(() => createPreviewSiteBridge(options)),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nfunction createPreviewHostBridge(options: PreviewHostBridgeOptions, runFork: typeof Effect.runFork) {\n const listeners = new Set<(message: PreviewSitePayload) => void>()\n let retry: ReturnType<typeof Effect.runFork> | undefined\n let state: PreviewBridgeState = 'connecting'\n\n const stopRetry = (): void => {\n if (retry === undefined) return\n retry.interruptUnsafe()\n retry = undefined\n }\n\n const post = (payload: StudioPreviewPayload): boolean => {\n const target = options.target()\n\n if (!target) return false\n target.postMessage(envelope(options.sessionId, payload), options.targetOrigin)\n\n return true\n }\n\n const connect = (): void => {\n post({ type: 'studio:connect' })\n }\n\n const reconnect = (): void => {\n if (state === 'closed') return\n state = 'connecting'\n stopRetry()\n const interval = options.handshakeIntervalMs ?? 250\n retry = runFork(Effect.sync(connect).pipe(Effect.repeat(Schedule.spaced(interval)), Effect.delay(interval)))\n connect()\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n const target = options.target()\n\n if (!target) return\n const message = receivePreviewMessage(event, target, options.targetOrigin, options.sessionId)\n\n if (!message || !isPreviewSiteMessage(message)) return\n\n if (message.type === 'preview:ready') {\n state = 'ready'\n stopRetry()\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (state === 'closed') return\n state = 'closed'\n stopRetry()\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n reconnect()\n\n return {\n close,\n reconnect,\n send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) =>\n state === 'ready' && post(payload),\n state: () => state,\n subscribe: (listener: (message: PreviewSitePayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction createPreviewSiteBridge(options: PreviewSiteBridgeOptions) {\n const listeners = new Set<(message: StudioPreviewPayload) => void>()\n let active = false\n let closed = false\n\n const post = (payload: PreviewSitePayload): boolean => {\n if (!active || closed) return false\n options.parent.postMessage(envelope(options.sessionId, payload), options.parentOrigin)\n\n return true\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n if (closed) return\n const message = receivePreviewMessage(event, options.parent, options.parentOrigin, options.sessionId)\n\n if (!message || !isStudioPreviewMessage(message)) return\n\n if (message.type === 'studio:connect') {\n active = true\n post(options.route)\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (closed) return\n closed = true\n active = false\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n\n return {\n close,\n send: post,\n subscribe: (listener: (message: StudioPreviewPayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction envelope(\n sessionId: PreviewSessionId,\n payload: PreviewSitePayload | StudioPreviewPayload,\n): DocumentPreviewMessage {\n return Result.getOrThrow(\n Schema.decodeResult(Schema.toType(DocumentPreviewMessageSchema))({\n channel: DOCUMENT_PREVIEW_CHANNEL,\n sessionId,\n ...payload,\n }),\n )\n}\n\nfunction receivePreviewMessage(\n event: PreviewMessageEvent,\n source: PreviewMessageTarget,\n origin: PreviewOrigin,\n sessionId: PreviewSessionId,\n): DocumentPreviewMessage | undefined {\n if (event.origin !== origin || event.source !== source || !isDocumentPreviewMessage(event.data)) return undefined\n\n return event.data.sessionId === sessionId ? event.data : undefined\n}\n\nfunction isPreviewSiteMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `preview:${string}` }> {\n return message.type.startsWith('preview:')\n}\n\nfunction isStudioPreviewMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `studio:${string}` }> {\n return message.type.startsWith('studio:')\n}\n","import type { PublicPath } from '@internal/protocol/Documents'\nimport type { PreviewOrigin, PreviewSessionId, PreviewSiteBridge } from '@internal/protocol/Preview'\nimport { makePreviewSiteBridge } from '@internal/protocol/Preview'\nimport { Effect, Exit, Scope } from 'effect'\n\nexport interface SitePreviewConnection extends PreviewSiteBridge {\n readonly close: () => Promise<void>\n}\n\nexport async function openSitePreviewConnection(options: {\n readonly editorOrigin: PreviewOrigin\n readonly route: PublicPath\n readonly sessionId: PreviewSessionId\n}): Promise<SitePreviewConnection> {\n const scope = await Effect.runPromise(Scope.make())\n\n try {\n const bridge = await Effect.runPromise(\n makePreviewSiteBridge({\n parent: window.parent,\n parentOrigin: options.editorOrigin,\n route: { path: options.route, type: 'preview:ready' },\n sessionId: options.sessionId,\n view: window,\n }).pipe(Effect.provideService(Scope.Scope, scope)),\n )\n\n let closed = false\n\n return {\n async close() {\n if (closed) return\n closed = true\n await Effect.runPromise(Scope.close(scope, Exit.void))\n },\n send: bridge.send,\n subscribe: bridge.subscribe,\n }\n } catch (cause) {\n await Effect.runPromise(Scope.close(scope, Exit.fail(cause)))\n throw cause\n }\n}\n"],"mappings":";;;;AAuEA,SAAgB,sBACd,SACsD;CACtD,OAAO,OAAO,eACZ,OAAO,WAAW,wBAAwB,OAAO,CAAC,IACjD,WAAW,OAAO,WAAW,OAAO,MAAM,CAAC,CAC9C,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,OAAO,QAAQ,GAAG,aAAa,MAAM,CAAC;AAC7D;AA4EA,SAAS,wBAAwB,SAAmC;CAClE,MAAM,4BAAY,IAAI,IAA6C;CACnE,IAAI,SAAS;CACb,IAAI,SAAS;CAEb,MAAM,QAAQ,YAAyC;EACrD,IAAI,CAAC,UAAU,QAAQ,OAAO;EAC9B,QAAQ,OAAO,YAAY,SAAS,QAAQ,WAAW,OAAO,GAAG,QAAQ,YAAY;EAErF,OAAO;CACT;CAEA,MAAM,WAAW,UAAqC;EACpD,IAAI,QAAQ;EACZ,MAAM,UAAU,sBAAsB,OAAO,QAAQ,QAAQ,QAAQ,cAAc,QAAQ,SAAS;EAEpG,IAAI,CAAC,WAAW,CAAC,uBAAuB,OAAO,GAAG;EAElD,IAAI,QAAQ,SAAS,kBAAkB;GACrC,SAAS;GACT,KAAK,QAAQ,KAAK;EACpB;EAEA,KAAK,MAAM,YAAY,WAAW,SAAS,OAAO;CACpD;CAEA,MAAM,cAAoB;EACxB,IAAI,QAAQ;EACZ,SAAS;EACT,SAAS;EACT,QAAQ,KAAK,oBAAoB,WAAW,OAAO;EACnD,UAAU,MAAM;CAClB;CAEA,QAAQ,KAAK,iBAAiB,WAAW,OAAO;CAEhD,OAAO;EACL;EACA,MAAM;EACN,YAAY,aAAsD;GAChE,UAAU,IAAI,QAAQ;GAEtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF;AACF;AAEA,SAAS,SACP,WACA,SACwB;CACxB,OAAO,OAAO,WACZ,OAAO,aAAa,OAAO,OAAOA,sBAA4B,CAAC,CAAC,CAAC;EAC/D,SAAS;EACT;EACA,GAAG;CACL,CAAC,CACH;AACF;AAEA,SAAS,sBACP,OACA,QACA,QACA,WACoC;CACpC,IAAI,MAAM,WAAW,UAAU,MAAM,WAAW,UAAU,CAAC,yBAAyB,MAAM,IAAI,GAAG,OAAO;CAExG,OAAO,MAAM,KAAK,cAAc,YAAY,MAAM,OAAO;AAC3D;AAQA,SAAS,uBACP,SACmF;CACnF,OAAO,QAAQ,KAAK,WAAW,SAAS;AAC1C;;;;AClOA,eAAsB,0BAA0B,SAIb;CACjC,MAAM,QAAQ,MAAM,OAAO,WAAW,MAAM,KAAK,CAAC;CAElD,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAC1B,sBAAsB;GACpB,QAAQ,OAAO;GACf,cAAc,QAAQ;GACtB,OAAO;IAAE,MAAM,QAAQ;IAAO,MAAM;GAAgB;GACpD,WAAW,QAAQ;GACnB,MAAM;EACR,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC,CACnD;EAEA,IAAI,SAAS;EAEb,OAAO;GACL,MAAM,QAAQ;IACZ,IAAI,QAAQ;IACZ,SAAS;IACT,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,IAAI,CAAC;GACvD;GACA,MAAM,OAAO;GACb,WAAW,OAAO;EACpB;CACF,SAAS,OAAO;EACd,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC;EAC5D,MAAM;CACR;AACF"}
|
|
1
|
+
{"version":3,"file":"connection-D5S9Iph-.js","names":["DocumentPreviewMessageSchema"],"sources":["../../../../protocol/src/internal/preview/bridge.ts","../../../src/runtime/preview/connection.ts"],"sourcesContent":["import type {\n DocumentPreviewMessage,\n PreviewOrigin,\n PreviewSessionId,\n PreviewSitePayload,\n StudioPreviewPayload,\n} from './models'\nimport type { Scope } from 'effect'\nimport { Effect, Result, Schedule, Schema } from 'effect'\nimport {\n DOCUMENT_PREVIEW_CHANNEL,\n DocumentPreviewMessage as DocumentPreviewMessageSchema,\n isDocumentPreviewMessage,\n} from './models'\n\nexport interface PreviewMessageEvent {\n readonly data: unknown\n readonly origin: string\n readonly source: object | null\n}\n\nexport interface PreviewMessageSource {\n readonly addEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n readonly removeEventListener: (type: 'message', listener: (event: PreviewMessageEvent) => void) => void\n}\n\nexport interface PreviewMessageTarget {\n readonly postMessage: (message: DocumentPreviewMessage, targetOrigin: string) => void\n}\n\nexport type PreviewBridgeState = 'closed' | 'connecting' | 'ready'\n\nexport interface PreviewHostBridge {\n readonly reconnect: () => void\n readonly send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) => boolean\n readonly state: () => PreviewBridgeState\n readonly subscribe: (listener: (message: PreviewSitePayload) => void) => () => void\n}\n\nexport interface PreviewSiteBridge {\n readonly send: (payload: Exclude<PreviewSitePayload, { readonly type: 'preview:ready' }>) => boolean\n readonly subscribe: (listener: (message: StudioPreviewPayload) => void) => () => void\n}\n\nexport interface PreviewHostBridgeOptions {\n readonly handshakeIntervalMs?: number\n readonly sessionId: PreviewSessionId\n readonly target: () => PreviewMessageTarget | undefined\n readonly targetOrigin: PreviewOrigin\n readonly view: PreviewMessageSource\n}\n\nexport interface PreviewSiteBridgeOptions {\n readonly parent: PreviewMessageTarget\n readonly parentOrigin: PreviewOrigin\n readonly route: PreviewSitePayload & { readonly type: 'preview:ready' }\n readonly sessionId: PreviewSessionId\n readonly view: PreviewMessageSource\n}\n\nexport function makePreviewHostBridge(\n options: PreviewHostBridgeOptions,\n): Effect.Effect<PreviewHostBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.context<never>().pipe(\n Effect.map((context) => createPreviewHostBridge(options, Effect.runForkWith(context))),\n ),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nexport function makePreviewSiteBridge(\n options: PreviewSiteBridgeOptions,\n): Effect.Effect<PreviewSiteBridge, never, Scope.Scope> {\n return Effect.acquireRelease(\n Effect.sync(() => createPreviewSiteBridge(options)),\n (bridge) => Effect.sync(() => bridge.close()),\n ).pipe(Effect.map(({ close: _close, ...bridge }) => bridge))\n}\n\nfunction createPreviewHostBridge(options: PreviewHostBridgeOptions, runFork: typeof Effect.runFork) {\n const listeners = new Set<(message: PreviewSitePayload) => void>()\n let retry: ReturnType<typeof Effect.runFork> | undefined\n let state: PreviewBridgeState = 'connecting'\n\n const stopRetry = (): void => {\n if (retry === undefined) return\n retry.interruptUnsafe()\n retry = undefined\n }\n\n const post = (payload: StudioPreviewPayload): boolean => {\n const target = options.target()\n\n if (!target) return false\n target.postMessage(envelope(options.sessionId, payload), options.targetOrigin)\n\n return true\n }\n\n const connect = (): void => {\n post({ type: 'studio:connect' })\n }\n\n const reconnect = (): void => {\n if (state === 'closed') return\n state = 'connecting'\n stopRetry()\n const interval = options.handshakeIntervalMs ?? 250\n retry = runFork(Effect.sync(connect).pipe(Effect.repeat(Schedule.spaced(interval)), Effect.delay(interval)))\n connect()\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n const target = options.target()\n\n if (!target) return\n const message = receivePreviewMessage(event, target, options.targetOrigin, options.sessionId)\n\n if (!message || !isPreviewSiteMessage(message)) return\n\n if (message.type === 'preview:ready') {\n state = 'ready'\n stopRetry()\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (state === 'closed') return\n state = 'closed'\n stopRetry()\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n reconnect()\n\n return {\n close,\n reconnect,\n send: (payload: Exclude<StudioPreviewPayload, { readonly type: 'studio:connect' }>) =>\n state === 'ready' && post(payload),\n state: () => state,\n subscribe: (listener: (message: PreviewSitePayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction createPreviewSiteBridge(options: PreviewSiteBridgeOptions) {\n const listeners = new Set<(message: StudioPreviewPayload) => void>()\n let active = false\n let closed = false\n\n const post = (payload: PreviewSitePayload): boolean => {\n if (!active || closed) return false\n options.parent.postMessage(envelope(options.sessionId, payload), options.parentOrigin)\n\n return true\n }\n\n const receive = (event: PreviewMessageEvent): void => {\n if (closed) return\n const message = receivePreviewMessage(event, options.parent, options.parentOrigin, options.sessionId)\n\n if (!message || !isStudioPreviewMessage(message)) return\n\n if (message.type === 'studio:connect') {\n active = true\n post(options.route)\n }\n\n for (const listener of listeners) listener(message)\n }\n\n const close = (): void => {\n if (closed) return\n closed = true\n active = false\n options.view.removeEventListener('message', receive)\n listeners.clear()\n }\n\n options.view.addEventListener('message', receive)\n\n return {\n close,\n send: post,\n subscribe: (listener: (message: StudioPreviewPayload) => void) => {\n listeners.add(listener)\n\n return () => listeners.delete(listener)\n },\n }\n}\n\nfunction envelope(\n sessionId: PreviewSessionId,\n payload: PreviewSitePayload | StudioPreviewPayload,\n): DocumentPreviewMessage {\n return Result.getOrThrow(\n Schema.decodeResult(Schema.toType(DocumentPreviewMessageSchema))({\n channel: DOCUMENT_PREVIEW_CHANNEL,\n sessionId,\n ...payload,\n }),\n )\n}\n\nfunction receivePreviewMessage(\n event: PreviewMessageEvent,\n source: PreviewMessageTarget,\n origin: PreviewOrigin,\n sessionId: PreviewSessionId,\n): DocumentPreviewMessage | undefined {\n if (event.origin !== origin || event.source !== source || !isDocumentPreviewMessage(event.data)) return undefined\n\n return event.data.sessionId === sessionId ? event.data : undefined\n}\n\nfunction isPreviewSiteMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `preview:${string}` }> {\n return message.type.startsWith('preview:')\n}\n\nfunction isStudioPreviewMessage(\n message: DocumentPreviewMessage,\n): message is Extract<DocumentPreviewMessage, { readonly type: `studio:${string}` }> {\n return message.type.startsWith('studio:')\n}\n","import type { PublicPath } from '@internal/protocol/Documents'\nimport type { PreviewOrigin, PreviewSessionId, PreviewSiteBridge } from '@internal/protocol/Preview'\nimport { makePreviewSiteBridge } from '@internal/protocol/Preview'\nimport { Effect, Exit, Scope } from 'effect'\n\nexport interface SitePreviewConnection extends PreviewSiteBridge {\n readonly close: () => Promise<void>\n}\n\nexport async function openSitePreviewConnection(options: {\n readonly editorOrigin: PreviewOrigin\n readonly route: PublicPath\n readonly sessionId: PreviewSessionId\n}): Promise<SitePreviewConnection> {\n const scope = await Effect.runPromise(Scope.make())\n\n try {\n const bridge = await Effect.runPromise(\n makePreviewSiteBridge({\n parent: window.parent,\n parentOrigin: options.editorOrigin,\n route: { path: options.route, type: 'preview:ready' },\n sessionId: options.sessionId,\n view: window,\n }).pipe(Effect.provideService(Scope.Scope, scope)),\n )\n\n let closed = false\n\n return {\n async close() {\n if (closed) return\n closed = true\n await Effect.runPromise(Scope.close(scope, Exit.void))\n },\n send: bridge.send,\n subscribe: bridge.subscribe,\n }\n } catch (cause) {\n await Effect.runPromise(Scope.close(scope, Exit.fail(cause)))\n throw cause\n }\n}\n"],"mappings":";;;;AAuEA,SAAgB,sBACd,SACsD;CACtD,OAAO,OAAO,eACZ,OAAO,WAAW,wBAAwB,OAAO,CAAC,IACjD,WAAW,OAAO,WAAW,OAAO,MAAM,CAAC,CAC9C,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,OAAO,QAAQ,GAAG,aAAa,MAAM,CAAC;AAC7D;AA4EA,SAAS,wBAAwB,SAAmC;CAClE,MAAM,4BAAY,IAAI,IAA6C;CACnE,IAAI,SAAS;CACb,IAAI,SAAS;CAEb,MAAM,QAAQ,YAAyC;EACrD,IAAI,CAAC,UAAU,QAAQ,OAAO;EAC9B,QAAQ,OAAO,YAAY,SAAS,QAAQ,WAAW,OAAO,GAAG,QAAQ,YAAY;EAErF,OAAO;CACT;CAEA,MAAM,WAAW,UAAqC;EACpD,IAAI,QAAQ;EACZ,MAAM,UAAU,sBAAsB,OAAO,QAAQ,QAAQ,QAAQ,cAAc,QAAQ,SAAS;EAEpG,IAAI,CAAC,WAAW,CAAC,uBAAuB,OAAO,GAAG;EAElD,IAAI,QAAQ,SAAS,kBAAkB;GACrC,SAAS;GACT,KAAK,QAAQ,KAAK;EACpB;EAEA,KAAK,MAAM,YAAY,WAAW,SAAS,OAAO;CACpD;CAEA,MAAM,cAAoB;EACxB,IAAI,QAAQ;EACZ,SAAS;EACT,SAAS;EACT,QAAQ,KAAK,oBAAoB,WAAW,OAAO;EACnD,UAAU,MAAM;CAClB;CAEA,QAAQ,KAAK,iBAAiB,WAAW,OAAO;CAEhD,OAAO;EACL;EACA,MAAM;EACN,YAAY,aAAsD;GAChE,UAAU,IAAI,QAAQ;GAEtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF;AACF;AAEA,SAAS,SACP,WACA,SACwB;CACxB,OAAO,OAAO,WACZ,OAAO,aAAa,OAAO,OAAOA,sBAA4B,CAAC,CAAC,CAAC;EAC/D,SAAS;EACT;EACA,GAAG;CACL,CAAC,CACH;AACF;AAEA,SAAS,sBACP,OACA,QACA,QACA,WACoC;CACpC,IAAI,MAAM,WAAW,UAAU,MAAM,WAAW,UAAU,CAAC,yBAAyB,MAAM,IAAI,GAAG,OAAO;CAExG,OAAO,MAAM,KAAK,cAAc,YAAY,MAAM,OAAO;AAC3D;AAQA,SAAS,uBACP,SACmF;CACnF,OAAO,QAAQ,KAAK,WAAW,SAAS;AAC1C;;;;AClOA,eAAsB,0BAA0B,SAIb;CACjC,MAAM,QAAQ,MAAM,OAAO,WAAW,MAAM,KAAK,CAAC;CAElD,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAC1B,sBAAsB;GACpB,QAAQ,OAAO;GACf,cAAc,QAAQ;GACtB,OAAO;IAAE,MAAM,QAAQ;IAAO,MAAM;GAAgB;GACpD,WAAW,QAAQ;GACnB,MAAM;EACR,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC,CACnD;EAEA,IAAI,SAAS;EAEb,OAAO;GACL,MAAM,QAAQ;IACZ,IAAI,QAAQ;IACZ,SAAS;IACT,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,IAAI,CAAC;GACvD;GACA,MAAM,OAAO;GACb,WAAW,OAAO;EACpB;CACF,SAAS,OAAO;EACd,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC;EAC5D,MAAM;CACR;AACF"}
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { l as CMS_READINESS_PATH, p as STUDIO_COLLABORATION_PATH, u as STUDIO_API_PATH } from "./HttpPaths-c9nLZ9H2.js";
|
|
2
|
+
import { createCmsContentClient, readCmsContentDirectory } from "./Content.js";
|
|
3
|
+
import { t as AuthoringCatalog } from "./Configuration-BU-UyGoH.js";
|
|
4
|
+
import { loadCmsConfiguration } from "./internal/cms-config.js";
|
|
5
|
+
import { cmsRuntimeConfigurationDefaults, cmsRuntimeConfigurationFromEnvironment, compileCmsConfiguration, resolveCmsInstallation } from "./internal/configuration.js";
|
|
6
|
+
import { a as embeddedMigrations, i as createCmsBetterAuth, n as scopedDatabaseUrl, t as installCms } from "./installation-BNuoRDOz.js";
|
|
7
|
+
import { Effect, Layer, References, Result, Schema } from "effect";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { loadNuxt, loadNuxtConfig } from "@nuxt/kit";
|
|
12
|
+
import { NodeCrypto, NodeFileSystem, NodePath } from "@effect/platform-node-shared";
|
|
13
|
+
import { DeleteObjectCommand, HeadBucketCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
14
|
+
import { setupDotenv } from "c12";
|
|
15
|
+
import { Pool } from "pg";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
|
|
18
|
+
//#region ../sql-pg/src/Migration.ts
|
|
19
|
+
/** Migration history used by development tooling to verify pending SQL without mutating the database. */
|
|
20
|
+
const postgresMigrations = Object.freeze(embeddedMigrations.map((migration) => Object.freeze({
|
|
21
|
+
createdAt: migration.folderMillis,
|
|
22
|
+
hash: migration.hash,
|
|
23
|
+
name: migration.name,
|
|
24
|
+
statements: Object.freeze([...migration.sql])
|
|
25
|
+
})));
|
|
26
|
+
var MigrationFailed = class extends Schema.TaggedError()("Postgres.MigrationFailed", {
|
|
27
|
+
cause: Schema.Defect(),
|
|
28
|
+
operation: Schema.String
|
|
29
|
+
}) {};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region ../auth/src/LocalDevelopment.ts
|
|
33
|
+
var LocalAdministratorFailed = class extends Schema.TaggedError()("LocalAdministrator.Failed", { cause: Schema.Defect() }) {};
|
|
34
|
+
/** Reconciles the one local-development administrator without exposing Better Auth's admin routes. */
|
|
35
|
+
const reconcileLocalAdministrator = Effect.fn("LocalAdministrator.reconcile")(function* (options) {
|
|
36
|
+
const auth = createCmsBetterAuth({
|
|
37
|
+
baseURL: options.baseUrl,
|
|
38
|
+
database: options.database,
|
|
39
|
+
secret: options.secret
|
|
40
|
+
});
|
|
41
|
+
return yield* Effect.tryPromise({
|
|
42
|
+
try: async () => {
|
|
43
|
+
const context = await auth.$context;
|
|
44
|
+
const existing = await context.internalAdapter.findUserByEmail(options.email, { includeAccounts: true });
|
|
45
|
+
if (!existing) {
|
|
46
|
+
const user = await context.internalAdapter.createUser({
|
|
47
|
+
email: options.email,
|
|
48
|
+
name: options.name,
|
|
49
|
+
role: "administrator"
|
|
50
|
+
}, { method: "admin" });
|
|
51
|
+
const password = await context.password.hash(options.password);
|
|
52
|
+
await context.internalAdapter.linkAccount({
|
|
53
|
+
accountId: user.id,
|
|
54
|
+
password,
|
|
55
|
+
providerId: "credential",
|
|
56
|
+
userId: user.id
|
|
57
|
+
});
|
|
58
|
+
return "created";
|
|
59
|
+
}
|
|
60
|
+
const isAdministrator = Schema.is(Schema.Struct({ role: Schema.String }))(existing.user) && existing.user.role === "administrator";
|
|
61
|
+
let changed = existing.user.name !== options.name || !isAdministrator;
|
|
62
|
+
if (changed) await context.internalAdapter.updateUser(existing.user.id, {
|
|
63
|
+
name: options.name,
|
|
64
|
+
role: "administrator"
|
|
65
|
+
});
|
|
66
|
+
const credential = existing.accounts.find((account) => account.providerId === "credential" && account.accountId === existing.user.id);
|
|
67
|
+
if (!(credential?.password ? await context.password.verify({
|
|
68
|
+
hash: credential.password,
|
|
69
|
+
password: options.password
|
|
70
|
+
}) : false)) {
|
|
71
|
+
const password = await context.password.hash(options.password);
|
|
72
|
+
if (credential) await context.internalAdapter.updatePassword(existing.user.id, password);
|
|
73
|
+
else await context.internalAdapter.createAccount({
|
|
74
|
+
accountId: existing.user.id,
|
|
75
|
+
password,
|
|
76
|
+
providerId: "credential",
|
|
77
|
+
userId: existing.user.id
|
|
78
|
+
});
|
|
79
|
+
changed = true;
|
|
80
|
+
}
|
|
81
|
+
return changed ? "updated" : "verified";
|
|
82
|
+
},
|
|
83
|
+
catch: (cause) => new LocalAdministratorFailed({ cause })
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/internal/dev-lifecycle.ts
|
|
89
|
+
const nodeLayer = Layer.merge(NodeCrypto.layer, Layer.merge(NodeFileSystem.layer, NodePath.layer));
|
|
90
|
+
const InitializationStatus = Schema.Struct({ needsAdministrator: Schema.optionalKey(Schema.Boolean) });
|
|
91
|
+
const CauseCarrier = Schema.Struct({ cause: Schema.Unknown });
|
|
92
|
+
var CmsLifecycleFailed = class extends Schema.TaggedError()("CmsLifecycleFailed", {
|
|
93
|
+
cause: Schema.Defect(),
|
|
94
|
+
message: Schema.String,
|
|
95
|
+
phase: Schema.String
|
|
96
|
+
}) {};
|
|
97
|
+
const isCmsLifecycleFailed = Schema.is(CmsLifecycleFailed);
|
|
98
|
+
async function doctorCmsDevelopment(rootDir, full = false, envFile) {
|
|
99
|
+
const checks = [];
|
|
100
|
+
let configuration;
|
|
101
|
+
try {
|
|
102
|
+
const project = await loadConfiguration(rootDir, envFile);
|
|
103
|
+
compileCmsConfiguration(project.authored.configuration);
|
|
104
|
+
configuration = resolveCmsInstallation(project.authored.configuration, project.runtime, {
|
|
105
|
+
revision: "doctor",
|
|
106
|
+
components: []
|
|
107
|
+
});
|
|
108
|
+
checks.push(pass("configuration", "Configuration loaded"));
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
checks.push(fail("configuration", "Configuration loaded", message(cause)));
|
|
111
|
+
return report(checks);
|
|
112
|
+
}
|
|
113
|
+
const pool = new Pool({ connectionString: configuration.database.url });
|
|
114
|
+
try {
|
|
115
|
+
await pool.query("select 1");
|
|
116
|
+
checks.push(pass("postgres", "PostgreSQL reachable"));
|
|
117
|
+
try {
|
|
118
|
+
const pending = await verifyMigrations(pool, configuration.database.schema);
|
|
119
|
+
checks.push(pending === 0 ? pass("migrations", "Database schema current") : fail("migrations", "Database schema current", `${pending} migration${pending === 1 ? " is" : "s are"} pending; run cms dev bootstrap.`));
|
|
120
|
+
} catch (cause) {
|
|
121
|
+
checks.push(fail("migrations", "Database schema current", message(cause)));
|
|
122
|
+
}
|
|
123
|
+
if (full) {
|
|
124
|
+
await pool.query("begin");
|
|
125
|
+
try {
|
|
126
|
+
await pool.query(`create table "${configuration.database.schema}"."__cms_doctor_${randomUUID().replaceAll("-", "")}" (id integer)`);
|
|
127
|
+
checks.push(pass("postgres-write", "PostgreSQL write access"));
|
|
128
|
+
} finally {
|
|
129
|
+
await pool.query("rollback");
|
|
130
|
+
}
|
|
131
|
+
} else checks.push(notVerified("postgres-write", "PostgreSQL write access", "Use --full for a transient write check."));
|
|
132
|
+
} catch (cause) {
|
|
133
|
+
checks.push(fail("postgres", "PostgreSQL reachable", message(cause)));
|
|
134
|
+
} finally {
|
|
135
|
+
await pool.end();
|
|
136
|
+
}
|
|
137
|
+
if (configuration.media === void 0) checks.push(pass("s3", "Media storage disabled"));
|
|
138
|
+
else {
|
|
139
|
+
const storage = configuration.media;
|
|
140
|
+
const client = s3Client(storage);
|
|
141
|
+
let publicProbeKey;
|
|
142
|
+
try {
|
|
143
|
+
await client.send(new HeadBucketCommand({ Bucket: storage.bucket }));
|
|
144
|
+
checks.push(pass("s3", "S3 bucket reachable"));
|
|
145
|
+
if (full) {
|
|
146
|
+
const relativeKey = `.doctor/${randomUUID()}`;
|
|
147
|
+
const key = `${storage.prefix}${relativeKey}`;
|
|
148
|
+
await client.send(new PutObjectCommand({
|
|
149
|
+
Bucket: storage.bucket,
|
|
150
|
+
Key: key,
|
|
151
|
+
Body: /* @__PURE__ */ new Uint8Array()
|
|
152
|
+
}));
|
|
153
|
+
publicProbeKey = key;
|
|
154
|
+
checks.push(pass("s3-write", "S3 write access"));
|
|
155
|
+
} else checks.push(notVerified("s3-write", "S3 write access", "Use --full for a transient write check."));
|
|
156
|
+
} catch (cause) {
|
|
157
|
+
checks.push(fail("s3", "S3 bucket reachable", message(cause)));
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
if (full && !publicProbeKey) checks.push(notVerified("media-url", "Public media URL reachable", "The transient S3 upload failed."));
|
|
161
|
+
else {
|
|
162
|
+
const url = publicProbeKey ? publicObjectUrl(storage.publicBaseUrl, publicProbeKey.slice(storage.prefix.length)) : new URL(storage.publicBaseUrl);
|
|
163
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
164
|
+
checks.push(response.ok || !full && response.status === 404 ? pass("media-url", "Public media URL reachable") : fail("media-url", "Public media URL reachable", `${url.href} returned HTTP ${response.status}.`));
|
|
165
|
+
}
|
|
166
|
+
} catch (cause) {
|
|
167
|
+
checks.push(fail("media-url", "Public media URL reachable", message(cause)));
|
|
168
|
+
} finally {
|
|
169
|
+
if (publicProbeKey) try {
|
|
170
|
+
await client.send(new DeleteObjectCommand({
|
|
171
|
+
Bucket: storage.bucket,
|
|
172
|
+
Key: publicProbeKey
|
|
173
|
+
}));
|
|
174
|
+
} catch (cause) {
|
|
175
|
+
checks.push(fail("s3-cleanup", "S3 doctor cleanup", message(cause)));
|
|
176
|
+
}
|
|
177
|
+
client.destroy();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const bundledStudio = new URL("../studio/host.js", import.meta.url);
|
|
181
|
+
const publishedStudio = new URL("../../studio/host.js", import.meta.url);
|
|
182
|
+
const workspaceStudio = new URL("../../../../apps/studio/dist/host.js", import.meta.url);
|
|
183
|
+
checks.push(existsSync(bundledStudio) || existsSync(publishedStudio) || existsSync(workspaceStudio) ? pass("assets", "CMS and Studio package assets complete") : fail("assets", "CMS and Studio package assets complete", "Run the package build before publishing."));
|
|
184
|
+
try {
|
|
185
|
+
const readiness = await fetch(new URL(CMS_READINESS_PATH, configuration.baseUrl), { signal: AbortSignal.timeout(2e3) });
|
|
186
|
+
checks.push(readiness.ok ? pass("server", "Running server readiness") : fail("server", "Running server readiness", `Returned HTTP ${readiness.status}.`));
|
|
187
|
+
if (readiness.ok) try {
|
|
188
|
+
await probeWebSocket(new URL(STUDIO_COLLABORATION_PATH, configuration.baseUrl));
|
|
189
|
+
checks.push(pass("websocket", "Running server WebSocket support"));
|
|
190
|
+
} catch (cause) {
|
|
191
|
+
checks.push(fail("websocket", "Running server WebSocket support", message(cause)));
|
|
192
|
+
}
|
|
193
|
+
else checks.push(notVerified("websocket", "Running server WebSocket support", "Readiness did not succeed."));
|
|
194
|
+
} catch {
|
|
195
|
+
checks.push(notVerified("server", "Running server readiness", "Start Nuxt to verify the readiness endpoint."));
|
|
196
|
+
checks.push(notVerified("websocket", "Running server WebSocket support", "Start Nuxt to verify WebSockets."));
|
|
197
|
+
}
|
|
198
|
+
return report(checks);
|
|
199
|
+
}
|
|
200
|
+
async function bootstrapCmsDevelopment(options) {
|
|
201
|
+
if (!options.email || !options.password) throw new Error("CMS_EMAIL and CMS_PASSWORD are required for bootstrap.");
|
|
202
|
+
const project = await inPhase("bootstrap.configuration", () => loadConfiguration(options.rootDir, options.envFile));
|
|
203
|
+
const authoring = await inPhase("bootstrap.authoring", () => prepareAuthoring(options.rootDir, options.verbose));
|
|
204
|
+
const resolved = inPhaseSync("bootstrap.configuration", () => resolveCmsInstallation(project.authored.configuration, project.runtime, authoring));
|
|
205
|
+
const installation = await inPhase("bootstrap.installation", () => Effect.runPromise(installCms(resolved).pipe(Effect.provideService(References.MinimumLogLevel, options.verbose ? "Info" : "None"))));
|
|
206
|
+
const localFetch = (input, init) => {
|
|
207
|
+
const request = input instanceof Request ? new Request(input, init) : new Request(input, init);
|
|
208
|
+
const headers = new Headers(request.headers);
|
|
209
|
+
headers.set("x-forwarded-for", "127.0.0.1");
|
|
210
|
+
return installation.fetch(new Request(request, { headers }));
|
|
211
|
+
};
|
|
212
|
+
try {
|
|
213
|
+
const status = await inPhase("bootstrap.initialization", () => localFetch(new URL(`${STUDIO_API_PATH}/initialization`, resolved.baseUrl)));
|
|
214
|
+
const state = await inPhase("bootstrap.initialization", async () => Result.getOrThrow(Schema.decodeUnknownResult(InitializationStatus)(await status.json())));
|
|
215
|
+
let initialized = false;
|
|
216
|
+
if (state.needsAdministrator) {
|
|
217
|
+
const claimed = await inPhase("bootstrap.initialization", () => localFetch(new URL(`${STUDIO_API_PATH}/initialization`, resolved.baseUrl), {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: { "content-type": "application/json" },
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
email: options.email,
|
|
222
|
+
initializationToken: resolved.initializationToken,
|
|
223
|
+
name: options.name,
|
|
224
|
+
password: options.password
|
|
225
|
+
})
|
|
226
|
+
}));
|
|
227
|
+
if (!claimed.ok) throw new CmsLifecycleFailed({
|
|
228
|
+
cause: /* @__PURE__ */ new Error(`Administrator initialization failed (${claimed.status}).`),
|
|
229
|
+
message: `Administrator initialization failed (${claimed.status}).`,
|
|
230
|
+
phase: "bootstrap.initialization"
|
|
231
|
+
});
|
|
232
|
+
initialized = true;
|
|
233
|
+
}
|
|
234
|
+
const administrator = initialized ? "created" : await inPhase("bootstrap.administrator", () => reconcileAdministrator(resolved, {
|
|
235
|
+
email: options.email,
|
|
236
|
+
name: options.name,
|
|
237
|
+
password: options.password
|
|
238
|
+
}));
|
|
239
|
+
const content = createCmsContentClient({
|
|
240
|
+
baseUrl: resolved.baseUrl,
|
|
241
|
+
credentials: {
|
|
242
|
+
email: options.email,
|
|
243
|
+
password: options.password
|
|
244
|
+
},
|
|
245
|
+
fetch: localFetch
|
|
246
|
+
});
|
|
247
|
+
if (!options.content) {
|
|
248
|
+
await inPhase("bootstrap.content", () => content.diff({ sources: [] }));
|
|
249
|
+
return {
|
|
250
|
+
administrator,
|
|
251
|
+
contentApplied: false
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const contentDirectory = options.content;
|
|
255
|
+
const source = await inPhase("bootstrap.content", () => readCmsContentDirectory(contentDirectory));
|
|
256
|
+
const plan = await inPhase("bootstrap.content", () => content.plan(source, {
|
|
257
|
+
overwrite: true,
|
|
258
|
+
publish: options.publish
|
|
259
|
+
}));
|
|
260
|
+
if (!plan.valid) {
|
|
261
|
+
const details = [...plan.conflicts.map((entry) => `${entry.sourcePath}: conflicts with the current CMS content`), ...plan.issues.map((issue) => `${issue.sourcePath ?? issue.key ?? "content"}${issue.field ? ` (${issue.field})` : ""}: ${issue.message}`)];
|
|
262
|
+
throw new CmsLifecycleFailed({
|
|
263
|
+
cause: /* @__PURE__ */ new Error(`Bootstrap content has conflicts or validation errors. ${details.join(" ")}`),
|
|
264
|
+
message: `Bootstrap content has conflicts or validation errors. ${details.join(" ")}`,
|
|
265
|
+
phase: "bootstrap.content"
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
await inPhase("bootstrap.content", () => content.apply(plan));
|
|
269
|
+
return {
|
|
270
|
+
administrator,
|
|
271
|
+
contentApplied: true
|
|
272
|
+
};
|
|
273
|
+
} finally {
|
|
274
|
+
await installation.close();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async function reconcileAdministrator(configuration, administrator) {
|
|
278
|
+
const pool = new Pool({ connectionString: scopedDatabaseUrl(configuration.database) });
|
|
279
|
+
try {
|
|
280
|
+
return await Effect.runPromise(reconcileLocalAdministrator({
|
|
281
|
+
baseUrl: configuration.baseUrl,
|
|
282
|
+
database: pool,
|
|
283
|
+
email: administrator.email,
|
|
284
|
+
name: administrator.name,
|
|
285
|
+
password: administrator.password,
|
|
286
|
+
secret: `cms-authentication:${configuration.secret}`
|
|
287
|
+
}));
|
|
288
|
+
} finally {
|
|
289
|
+
await pool.end();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function resetCmsDevelopment(options) {
|
|
293
|
+
const project = await inPhase("reset.configuration", () => loadConfiguration(options.rootDir, options.envFile));
|
|
294
|
+
const configuration = inPhaseSync("reset.configuration", () => resolveCmsInstallation(project.authored.configuration, project.runtime, {
|
|
295
|
+
revision: "reset",
|
|
296
|
+
components: []
|
|
297
|
+
}));
|
|
298
|
+
const targets = [...options.database ? [`PostgreSQL schema ${configuration.database.schema}`] : [], ...options.media && configuration.media !== void 0 ? [`S3 prefix ${configuration.media.bucket}/${configuration.media.prefix}`] : []];
|
|
299
|
+
if (!options.yes) return {
|
|
300
|
+
executed: false,
|
|
301
|
+
targets,
|
|
302
|
+
bootstrapped: false
|
|
303
|
+
};
|
|
304
|
+
await inPhase("reset.safety", () => refuseRunningInstallation(configuration.baseUrl));
|
|
305
|
+
if (options.database) await inPhase("reset.database", () => resetDatabase(configuration.database));
|
|
306
|
+
const media = configuration.media;
|
|
307
|
+
if (options.media && media !== void 0) await inPhase("reset.media", () => resetMedia(media));
|
|
308
|
+
if (options.bootstrap) await bootstrapCmsDevelopment({
|
|
309
|
+
rootDir: options.rootDir,
|
|
310
|
+
envFile: options.envFile,
|
|
311
|
+
verbose: options.verbose,
|
|
312
|
+
...options.bootstrap
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
executed: true,
|
|
316
|
+
targets,
|
|
317
|
+
bootstrapped: options.bootstrap !== void 0
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
async function loadCmsProjectEnvironment(rootDir, envFile) {
|
|
321
|
+
await setupDotenv({
|
|
322
|
+
cwd: resolve(rootDir),
|
|
323
|
+
fileName: envFile ? [".env", envFile] : ".env"
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async function loadConfiguration(rootDir, envFile) {
|
|
327
|
+
const absoluteRoot = resolve(rootDir);
|
|
328
|
+
await loadCmsProjectEnvironment(absoluteRoot, envFile);
|
|
329
|
+
const authored = await Effect.runPromise(loadCmsConfiguration(absoluteRoot).pipe(Effect.provide(nodeLayer)));
|
|
330
|
+
const nuxt = await loadNuxtConfig({
|
|
331
|
+
cwd: absoluteRoot,
|
|
332
|
+
dotenv: false
|
|
333
|
+
});
|
|
334
|
+
const defaults = cmsRuntimeConfigurationDefaults(authored.configuration, nuxt.runtimeConfig.cms);
|
|
335
|
+
return {
|
|
336
|
+
authored,
|
|
337
|
+
runtime: await Effect.runPromise(cmsRuntimeConfigurationFromEnvironment(defaults))
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function prepareAuthoring(rootDir, verbose = false) {
|
|
341
|
+
const nuxt = await loadNuxt({
|
|
342
|
+
cwd: rootDir,
|
|
343
|
+
dev: true,
|
|
344
|
+
ready: true,
|
|
345
|
+
overrides: {
|
|
346
|
+
logLevel: verbose ? "verbose" : "silent",
|
|
347
|
+
...verbose ? void 0 : {
|
|
348
|
+
devtools: { enabled: false },
|
|
349
|
+
seo: { metaDataFiles: false }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
try {
|
|
354
|
+
const source = (await readFile(resolve(nuxt.options.buildDir, "cms/authoring.mjs"), "utf8")).replace(/^export default /u, "").trim();
|
|
355
|
+
return Result.getOrThrow(Schema.decodeUnknownResult(AuthoringCatalog)(JSON.parse(source)));
|
|
356
|
+
} finally {
|
|
357
|
+
await nuxt.close();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function refuseRunningInstallation(baseUrl) {
|
|
361
|
+
try {
|
|
362
|
+
await fetch(new URL(CMS_READINESS_PATH, baseUrl), { signal: AbortSignal.timeout(1e3) });
|
|
363
|
+
throw new Error("Refusing to reset while the CMS server is running.");
|
|
364
|
+
} catch (cause) {
|
|
365
|
+
if (cause instanceof Error && cause.message.startsWith("Refusing")) throw cause;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function verifyMigrations(pool, schema) {
|
|
369
|
+
const applied = (await pool.query("select exists(select 1 from information_schema.tables where table_schema = $1 and table_name = $2) as exists", [schema, "__drizzle_migrations"])).rows[0]?.exists ? (await pool.query(`select created_at as "createdAt", hash from "${schema}"."__drizzle_migrations" order by created_at`)).rows : [];
|
|
370
|
+
if (!applied.every((migration, index) => migration.hash === postgresMigrations[index]?.hash && Number(migration.createdAt) === postgresMigrations[index]?.createdAt) || applied.length > postgresMigrations.length) throw new Error("The applied migration history does not match this CMS release.");
|
|
371
|
+
const pending = postgresMigrations.slice(applied.length);
|
|
372
|
+
const connection = await pool.connect();
|
|
373
|
+
try {
|
|
374
|
+
await connection.query("begin");
|
|
375
|
+
await connection.query(`create schema if not exists "${schema}"`);
|
|
376
|
+
await connection.query(`set local search_path to "${schema}"`);
|
|
377
|
+
for (const migration of pending) for (const statement of migration.statements) await connection.query(statement);
|
|
378
|
+
} finally {
|
|
379
|
+
await connection.query("rollback");
|
|
380
|
+
connection.release();
|
|
381
|
+
}
|
|
382
|
+
return pending.length;
|
|
383
|
+
}
|
|
384
|
+
function publicObjectUrl(baseUrl, key) {
|
|
385
|
+
const base = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
386
|
+
return new URL(key.split("/").map(encodeURIComponent).join("/"), base);
|
|
387
|
+
}
|
|
388
|
+
async function probeWebSocket(httpUrl) {
|
|
389
|
+
const url = new URL(httpUrl);
|
|
390
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
391
|
+
await new Promise((resolve, reject) => {
|
|
392
|
+
const socket = new WebSocket(url);
|
|
393
|
+
const timeout = setTimeout(() => {
|
|
394
|
+
socket.close();
|
|
395
|
+
reject(/* @__PURE__ */ new Error("The WebSocket handshake timed out."));
|
|
396
|
+
}, 2e3);
|
|
397
|
+
socket.addEventListener("open", () => {
|
|
398
|
+
clearTimeout(timeout);
|
|
399
|
+
socket.close();
|
|
400
|
+
resolve();
|
|
401
|
+
});
|
|
402
|
+
socket.addEventListener("error", () => {
|
|
403
|
+
clearTimeout(timeout);
|
|
404
|
+
reject(/* @__PURE__ */ new Error(`Could not open ${url.href}.`));
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
async function resetDatabase(database) {
|
|
409
|
+
const pool = new Pool({ connectionString: database.url });
|
|
410
|
+
try {
|
|
411
|
+
const client = await pool.connect();
|
|
412
|
+
try {
|
|
413
|
+
await client.query("select pg_advisory_lock(hashtextextended($1, 0))", ["cms:development-reset:v1"]);
|
|
414
|
+
await client.query(`drop schema if exists "${database.schema}" cascade`);
|
|
415
|
+
await client.query(`create schema "${database.schema}"`);
|
|
416
|
+
} finally {
|
|
417
|
+
client.release();
|
|
418
|
+
}
|
|
419
|
+
} finally {
|
|
420
|
+
await pool.end();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async function resetMedia(storage) {
|
|
424
|
+
const client = s3Client(storage);
|
|
425
|
+
try {
|
|
426
|
+
let continuationToken;
|
|
427
|
+
do {
|
|
428
|
+
const page = await client.send(new ListObjectsV2Command({
|
|
429
|
+
Bucket: storage.bucket,
|
|
430
|
+
Prefix: storage.prefix,
|
|
431
|
+
ContinuationToken: continuationToken
|
|
432
|
+
}));
|
|
433
|
+
for (const object of page.Contents ?? []) if (object.Key) await client.send(new DeleteObjectCommand({
|
|
434
|
+
Bucket: storage.bucket,
|
|
435
|
+
Key: object.Key
|
|
436
|
+
}));
|
|
437
|
+
continuationToken = page.IsTruncated ? page.NextContinuationToken : void 0;
|
|
438
|
+
} while (continuationToken);
|
|
439
|
+
} finally {
|
|
440
|
+
client.destroy();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function s3Client(storage) {
|
|
444
|
+
return new S3Client({
|
|
445
|
+
region: storage.region,
|
|
446
|
+
endpoint: storage.endpoint,
|
|
447
|
+
forcePathStyle: storage.forcePathStyle,
|
|
448
|
+
credentials: storage.credentials
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function pass(id, label) {
|
|
452
|
+
return {
|
|
453
|
+
id,
|
|
454
|
+
label,
|
|
455
|
+
phase: `doctor.${id}`,
|
|
456
|
+
status: "pass"
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function fail(id, label, detail) {
|
|
460
|
+
return {
|
|
461
|
+
id,
|
|
462
|
+
label,
|
|
463
|
+
phase: `doctor.${id}`,
|
|
464
|
+
status: "fail",
|
|
465
|
+
detail
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function notVerified(id, label, detail) {
|
|
469
|
+
return {
|
|
470
|
+
id,
|
|
471
|
+
label,
|
|
472
|
+
phase: `doctor.${id}`,
|
|
473
|
+
status: "not-verified",
|
|
474
|
+
detail
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function report(checks) {
|
|
478
|
+
return {
|
|
479
|
+
ok: checks.every((check) => check.status !== "fail"),
|
|
480
|
+
checks
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function message(cause) {
|
|
484
|
+
if (cause instanceof Error && cause.message) return cause.message;
|
|
485
|
+
if (Schema.is(CauseCarrier)(cause)) return message(cause.cause);
|
|
486
|
+
return String(cause);
|
|
487
|
+
}
|
|
488
|
+
async function inPhase(phase, operation) {
|
|
489
|
+
try {
|
|
490
|
+
return await operation();
|
|
491
|
+
} catch (cause) {
|
|
492
|
+
if (isCmsLifecycleFailed(cause)) throw cause;
|
|
493
|
+
throw new CmsLifecycleFailed({
|
|
494
|
+
cause,
|
|
495
|
+
message: message(cause),
|
|
496
|
+
phase
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function inPhaseSync(phase, operation) {
|
|
501
|
+
try {
|
|
502
|
+
return operation();
|
|
503
|
+
} catch (cause) {
|
|
504
|
+
if (isCmsLifecycleFailed(cause)) throw cause;
|
|
505
|
+
throw new CmsLifecycleFailed({
|
|
506
|
+
cause,
|
|
507
|
+
message: message(cause),
|
|
508
|
+
phase
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
//#endregion
|
|
514
|
+
export { resetCmsDevelopment as i, doctorCmsDevelopment as n, loadCmsProjectEnvironment as r, bootstrapCmsDevelopment as t };
|
|
515
|
+
//# sourceMappingURL=dev-lifecycle-CZ7iCzG9.js.map
|