@nuasite/cms 0.43.0-beta.2 → 0.43.0-beta.3

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/editor.js CHANGED
@@ -386,7 +386,7 @@ function fC(t, e) {
386
386
  function mC(t, e) {
387
387
  return typeof e == "function" ? e(t) : e;
388
388
  }
389
- const W3 = "0.43.0-beta.2", K3 = W3, ct = {
389
+ const W3 = "0.43.0-beta.3", K3 = W3, ct = {
390
390
  /** Highlight overlay for hovered elements */
391
391
  HIGHLIGHT: 2147483644,
392
392
  /** Hover outline for elements/components */
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "directory": "packages/astro-cms"
15
15
  },
16
16
  "license": "Apache-2.0",
17
- "version": "0.43.0-beta.2",
17
+ "version": "0.43.0-beta.3",
18
18
  "module": "src/index.ts",
19
19
  "types": "src/index.ts",
20
20
  "type": "module",
@@ -26,16 +26,16 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@nuasite/cms-core": "0.43.0-beta.2",
30
- "@nuasite/cms-types": "0.43.0-beta.2",
31
- "@nuasite/cms-sidecar": "0.43.0-beta.2",
32
- "@nuasite/collections-admin": "0.43.0-beta.2",
29
+ "@nuasite/cms-core": "0.43.0-beta.3",
30
+ "@nuasite/cms-types": "0.43.0-beta.3",
33
31
  "@astrojs/compiler": "^3.0.1",
34
32
  "@babel/parser": "^7.29.2",
35
33
  "node-html-parser": "^7.1.0",
36
34
  "yaml": "^2.8.3"
37
35
  },
38
36
  "devDependencies": {
37
+ "@nuasite/cms-sidecar": "0.43.0-beta.3",
38
+ "@nuasite/collections-admin": "0.43.0-beta.3",
39
39
  "@babel/types": "^7.29.0",
40
40
  "@types/react": "^19.2.7",
41
41
  "@types/react-dom": "^19.2.3",
@@ -72,6 +72,8 @@
72
72
  "typescript": "^6.0.2",
73
73
  "vite": "^7.0.0",
74
74
  "@aws-sdk/client-s3": "^3.0.0",
75
+ "@nuasite/cms-sidecar": "0.43.0-beta.3",
76
+ "@nuasite/collections-admin": "0.43.0-beta.3",
75
77
  "react": "^19.0.0",
76
78
  "react-dom": "^19.0.0"
77
79
  },
@@ -79,6 +81,12 @@
79
81
  "@aws-sdk/client-s3": {
80
82
  "optional": true
81
83
  },
84
+ "@nuasite/cms-sidecar": {
85
+ "optional": true
86
+ },
87
+ "@nuasite/collections-admin": {
88
+ "optional": true
89
+ },
82
90
  "react": {
83
91
  "optional": true
84
92
  },
@@ -25,7 +25,13 @@
25
25
  */
26
26
 
27
27
  import { createCmsCore, createNodeFs } from '@nuasite/cms-core'
28
- import { type CmsSidecarServer, createServer } from '@nuasite/cms-sidecar'
28
+ // `@nuasite/cms-sidecar` is an OPTIONAL peer (cms-headless F6 slim): a generated
29
+ // site that depends on `@nuasite/cms` must not pull the sidecar (+ collections-admin
30
+ // + React) into its resolved tree. The TYPES are imported `import type` (erased at
31
+ // build, so no runtime dependency edge), and the VALUES are loaded lazily via a
32
+ // dynamic `import()` only when local mode actually serves `/_nua/admin`. Hosted
33
+ // (sandbox) mode never registers this middleware, so it never imports the peer.
34
+ import type { CmsSidecarServer, createServer as CreateSidecarServer } from '@nuasite/cms-sidecar'
29
35
  import type { IncomingMessage, ServerResponse } from 'node:http'
30
36
  import { getProjectRoot } from './config'
31
37
  import { readBody } from './handlers/request-utils'
@@ -50,6 +56,13 @@ export interface LocalAdminOptions {
50
56
  maxUploadSize: number
51
57
  /** Virtual-module id of the SPA entry the HTML shell loads (transformed by Vite). */
52
58
  entryModuleId: string
59
+ /**
60
+ * Loader for the optional peers (cms-headless F6). Defaults to the real
61
+ * dynamic-`import()` loader; injectable so a test can simulate the peers being
62
+ * absent. The peers are loaded lazily on first `/_nua/admin` hit, never on
63
+ * `pletivo dev` startup.
64
+ */
65
+ peerLoader?: AdminPeerLoader
53
66
  }
54
67
 
55
68
  /** URL the collections-admin SPA is served at. */
@@ -61,6 +74,54 @@ export const ADMIN_API_PREFIX = '/_nua/cms-admin-api'
61
74
  /** `apiBase` passed to the SPA — the sidecar serves its routes under `/cms/v1`. */
62
75
  export const ADMIN_API_BASE = `${ADMIN_API_PREFIX}/cms/v1`
63
76
 
77
+ /**
78
+ * Message shown when local mode tries to open `/_nua/admin` but the optional
79
+ * peers (`@nuasite/cms-sidecar` + `@nuasite/collections-admin`) are not installed.
80
+ * The marker pipeline + inline editing keep working — only the full-page admin is
81
+ * unavailable until the peers are added (or the site is run via pletivo).
82
+ */
83
+ export const ADMIN_PEERS_MISSING_MESSAGE =
84
+ 'Local CMS admin requires @nuasite/cms-sidecar and @nuasite/collections-admin — install them (or run via pletivo) to enable /_nua/admin.'
85
+
86
+ /**
87
+ * Loader seam for the two optional peers (cms-headless F6). The default
88
+ * implementation loads them lazily via dynamic `import()` — the only dynamic
89
+ * imports in this package (user-approved) — keeping them out of a slim generated
90
+ * site's resolved tree. Injectable so a test can simulate the peers being absent
91
+ * without uninstalling them from the workspace.
92
+ */
93
+ export interface AdminPeerLoader {
94
+ /** Resolve `@nuasite/cms-sidecar`'s `createServer`, or `null` if the peer is absent. */
95
+ loadCreateSidecar(): Promise<typeof CreateSidecarServer | null>
96
+ /** Whether `@nuasite/collections-admin` (the SPA the shell mounts) is installed. */
97
+ isCollectionsAdminInstalled(): Promise<boolean>
98
+ }
99
+
100
+ /**
101
+ * Default peer loader: lazily `import()` the optional peers on first use. The
102
+ * module ids are bound to `const`s so they are NOT static-import string literals —
103
+ * a slim site that doesn't install the peers never resolves them, and the import
104
+ * only runs in local mode on the first `/_nua/admin` (or admin-api) hit. Both
105
+ * rejections (peer absent) degrade to `null` / `false` rather than throwing.
106
+ */
107
+ export const defaultAdminPeerLoader: AdminPeerLoader = {
108
+ async loadCreateSidecar() {
109
+ const moduleId = '@nuasite/cms-sidecar'
110
+ const loaded = await import(moduleId).then(
111
+ (mod: { createServer: typeof CreateSidecarServer }) => mod,
112
+ () => null,
113
+ )
114
+ return loaded === null ? null : loaded.createServer
115
+ },
116
+ async isCollectionsAdminInstalled() {
117
+ const moduleId = '@nuasite/collections-admin'
118
+ return import(moduleId).then(
119
+ () => true,
120
+ () => false,
121
+ )
122
+ },
123
+ }
124
+
64
125
  /**
65
126
  * Build the HTML shell for the admin SPA. It loads the virtual entry module
66
127
  * (which imports the lib's stylesheet and mounts `<CollectionsAdminApp apiBase={…} />`),
@@ -93,11 +154,17 @@ function adminShellHtml(entryModuleId: string, apiBase: string): string {
93
154
  * Lazily create the in-process cms-sidecar over the project's `node:fs`. The core
94
155
  * is built once on first use and reused; the media adapter mirrors the dev
95
156
  * server's selection (local `public/uploads` by default in this mode).
157
+ *
158
+ * Returns `null` when the optional `@nuasite/cms-sidecar` peer is not installed —
159
+ * callers serve the {@link ADMIN_PEERS_MISSING_MESSAGE} placeholder rather than
160
+ * crashing. The dynamic `import()` of the peer happens here, on first use only.
96
161
  */
97
- function makeLazySidecar(options: LocalAdminOptions): () => CmsSidecarServer {
162
+ function makeLazySidecar(options: LocalAdminOptions, peerLoader: AdminPeerLoader): () => Promise<CmsSidecarServer | null> {
98
163
  let server: CmsSidecarServer | null = null
99
- return () => {
164
+ return async () => {
100
165
  if (server) return server
166
+ const createServer = await peerLoader.loadCreateSidecar()
167
+ if (createServer === null) return null
101
168
  const root = getProjectRoot()
102
169
  const fs = createNodeFs(root)
103
170
  const core = createCmsCore(fs, {
@@ -161,7 +228,8 @@ async function writeWebResponse(res: ServerResponse, response: Response): Promis
161
228
  * registers this middleware (verified by the no-op test).
162
229
  */
163
230
  export function createLocalAdminMiddleware(server: AdminViteServerLike, options: LocalAdminOptions): void {
164
- const getSidecar = makeLazySidecar(options)
231
+ const peerLoader = options.peerLoader ?? defaultAdminPeerLoader
232
+ const getSidecar = makeLazySidecar(options, peerLoader)
165
233
  const shell = adminShellHtml(options.entryModuleId, ADMIN_API_BASE)
166
234
 
167
235
  // 1. In-process sidecar API. Strip the local mount prefix and forward the rest
@@ -176,7 +244,14 @@ export function createLocalAdminMiddleware(server: AdminViteServerLike, options:
176
244
 
177
245
  readBody(req, options.maxUploadSize)
178
246
  .then(async (body) => {
179
- const sidecar = getSidecar()
247
+ const sidecar = await getSidecar()
248
+ if (sidecar === null) {
249
+ // Optional peer not installed — degrade gracefully (cms-headless F6).
250
+ res.statusCode = 501
251
+ res.setHeader('content-type', 'application/json; charset=utf-8')
252
+ res.end(JSON.stringify({ error: ADMIN_PEERS_MISSING_MESSAGE, code: 'unsupported' }))
253
+ return
254
+ }
180
255
  const request = toWebRequest(req, forwardedPath, body)
181
256
  const response = await sidecar.fetch(request)
182
257
  await writeWebResponse(res, response)
@@ -205,7 +280,16 @@ export function createLocalAdminMiddleware(server: AdminViteServerLike, options:
205
280
  }
206
281
 
207
282
  const serve = async () => {
208
- const sidecar = getSidecar()
283
+ const sidecar = await getSidecar()
284
+ // Optional peers not installed — serve a clear placeholder rather than a
285
+ // blank/erroring SPA (cms-headless F6). Both halves of the pair are needed:
286
+ // the sidecar (API brain) and collections-admin (the SPA the shell mounts).
287
+ if (sidecar === null || !(await peerLoader.isCollectionsAdminInstalled())) {
288
+ res.statusCode = 501
289
+ res.setHeader('content-type', 'text/plain; charset=utf-8')
290
+ res.end(ADMIN_PEERS_MISSING_MESSAGE)
291
+ return
292
+ }
209
293
  const health = await sidecar.fetch(new Request('http://localhost/health'))
210
294
  if (!health.ok) {
211
295
  res.statusCode = 503
package/src/tsconfig.json CHANGED
@@ -26,7 +26,6 @@
26
26
  "references": [
27
27
  { "path": "../../cms-core/src" },
28
28
  { "path": "../../cms-types/src" },
29
- { "path": "../../collections-admin/src" },
30
29
  { "path": "../../cms-sidecar/src" },
31
30
  ]
32
31
  }