domma-cms 0.44.0 → 0.45.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.
Files changed (254) hide show
  1. package/CLAUDE.md +53 -53
  2. package/README.md +27 -27
  3. package/admin/css/admin.css +1 -1
  4. package/admin/dist/domma/domma-tools.css +3 -3
  5. package/admin/dist/domma/domma-tools.min.js +3 -3
  6. package/admin/index.html +2 -2
  7. package/admin/js/app.js +1 -1
  8. package/admin/js/lib/card-builder.js +2 -2
  9. package/admin/js/lib/crud-tutorial.js +1 -1
  10. package/admin/js/lib/effect-defs.js +1 -1
  11. package/admin/js/lib/effects-builder.js +1 -1
  12. package/admin/js/lib/image-editor.js +1 -1
  13. package/admin/js/lib/markdown-toolbar.js +2 -2
  14. package/admin/js/lib/project-context.js +1 -1
  15. package/admin/js/lib/project-quick-create.js +1 -1
  16. package/admin/js/lib/scribe-composer.js +1 -1
  17. package/admin/js/lib/server-restart.js +1 -1
  18. package/admin/js/lib/shortcode-context-menu.js +2 -2
  19. package/admin/js/lib/simple-editor.js +1 -1
  20. package/admin/js/lib/themes.js +1 -1
  21. package/admin/js/lib/timeline-builder.js +1 -1
  22. package/admin/js/templates/action-editor.html +8 -8
  23. package/admin/js/templates/api-endpoint-editor.html +7 -7
  24. package/admin/js/templates/api-endpoints.html +1 -1
  25. package/admin/js/templates/api-reference.html +1 -1
  26. package/admin/js/templates/block-editor.html +6 -6
  27. package/admin/js/templates/collection-editor.html +1 -1
  28. package/admin/js/templates/component-editor.html +2 -2
  29. package/admin/js/templates/dashboard/cache.html +3 -3
  30. package/admin/js/templates/dashboard/journeys.html +3 -3
  31. package/admin/js/templates/dashboard/kpi-strip.html +3 -3
  32. package/admin/js/templates/dashboard/top-pages.html +1 -1
  33. package/admin/js/templates/dashboard/traffic-chart.html +1 -1
  34. package/admin/js/templates/docs/api-actions.html +1 -1
  35. package/admin/js/templates/docs/api-collections.html +4 -4
  36. package/admin/js/templates/docs/api-pages.html +2 -2
  37. package/admin/js/templates/docs/api-plugins.html +1 -1
  38. package/admin/js/templates/docs/api-settings.html +2 -2
  39. package/admin/js/templates/docs/api-users.html +1 -1
  40. package/admin/js/templates/docs/components-howto.html +4 -4
  41. package/admin/js/templates/docs/components-reference.html +21 -21
  42. package/admin/js/templates/docs/components-rules.html +14 -14
  43. package/admin/js/templates/docs/components-walkthrough.html +13 -13
  44. package/admin/js/templates/docs/tutorial-crud.html +49 -49
  45. package/admin/js/templates/docs/tutorial-forms.html +4 -4
  46. package/admin/js/templates/docs/tutorial-plugin.html +10 -10
  47. package/admin/js/templates/docs/usage-actions.html +6 -6
  48. package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
  49. package/admin/js/templates/docs/usage-dconfig.html +4 -4
  50. package/admin/js/templates/docs/usage-navigation.html +1 -1
  51. package/admin/js/templates/docs/usage-pages.html +1 -1
  52. package/admin/js/templates/docs/usage-shortcodes.html +24 -24
  53. package/admin/js/templates/docs/usage-users-roles.html +3 -3
  54. package/admin/js/templates/docs/usage-views.html +4 -4
  55. package/admin/js/templates/documentation.html +1 -1
  56. package/admin/js/templates/effects.html +752 -752
  57. package/admin/js/templates/form-editor.html +5 -5
  58. package/admin/js/templates/forms.html +17 -17
  59. package/admin/js/templates/login.html +5 -5
  60. package/admin/js/templates/menu-editor.html +10 -10
  61. package/admin/js/templates/my-profile.html +17 -17
  62. package/admin/js/templates/navigation.html +11 -11
  63. package/admin/js/templates/notifications.html +1 -1
  64. package/admin/js/templates/page-editor.html +4 -4
  65. package/admin/js/templates/pro-docs.html +14 -14
  66. package/admin/js/templates/role-editor.html +70 -70
  67. package/admin/js/templates/roles.html +10 -10
  68. package/admin/js/templates/settings.html +10 -10
  69. package/admin/js/templates/tutorials.html +3 -3
  70. package/admin/js/templates/view-editor.html +15 -15
  71. package/admin/js/views/action-editor.js +1 -1
  72. package/admin/js/views/actions-list.js +1 -1
  73. package/admin/js/views/api-endpoint-editor.js +2 -2
  74. package/admin/js/views/api-endpoints.js +2 -2
  75. package/admin/js/views/api-tokens.js +3 -3
  76. package/admin/js/views/blocks.js +2 -2
  77. package/admin/js/views/collection-editor.js +1 -1
  78. package/admin/js/views/collection-entries.js +1 -1
  79. package/admin/js/views/collections.js +1 -1
  80. package/admin/js/views/component-editor.js +1 -1
  81. package/admin/js/views/components.js +4 -4
  82. package/admin/js/views/dashboard/widgets/cache.js +1 -1
  83. package/admin/js/views/dashboard/widgets/journeys.js +1 -1
  84. package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
  85. package/admin/js/views/form-editor.js +7 -7
  86. package/admin/js/views/form-submissions.js +1 -1
  87. package/admin/js/views/forms.js +1 -1
  88. package/admin/js/views/layouts.js +1 -1
  89. package/admin/js/views/login.js +2 -2
  90. package/admin/js/views/menu-editor.js +3 -3
  91. package/admin/js/views/menu-locations.js +1 -1
  92. package/admin/js/views/menus.js +3 -3
  93. package/admin/js/views/navigation.js +8 -8
  94. package/admin/js/views/page-editor.js +40 -40
  95. package/admin/js/views/pages.js +3 -3
  96. package/admin/js/views/plugin-code.js +2 -2
  97. package/admin/js/views/plugins.js +1 -1
  98. package/admin/js/views/project-settings.js +1 -1
  99. package/admin/js/views/projects.js +1 -1
  100. package/admin/js/views/roles.js +1 -1
  101. package/admin/js/views/user-editor.js +1 -1
  102. package/admin/js/views/users.js +3 -3
  103. package/admin/js/views/view-editor.js +1 -1
  104. package/admin/js/views/view-preview.js +1 -1
  105. package/admin/js/views/views-list.js +1 -1
  106. package/bin/cli.js +8 -8
  107. package/bin/lib/config-merge.js +44 -44
  108. package/bin/update.js +16 -16
  109. package/config/plugins.json +1 -1
  110. package/package.json +2 -2
  111. package/plugins/_template/admin/views/index.js +1 -1
  112. package/plugins/_template/config.js +1 -1
  113. package/plugins/_template/plugin.js +1 -1
  114. package/plugins/analytics/admin/templates/analytics.html +3 -3
  115. package/plugins/analytics/admin/views/analytics.js +1 -1
  116. package/plugins/analytics/config.js +1 -1
  117. package/plugins/analytics/plugin.js +4 -4
  118. package/plugins/analytics/plugin.json +1 -1
  119. package/plugins/analytics/public/inject-body.html +2 -2
  120. package/plugins/analytics/public/inject-head.html +1 -1
  121. package/plugins/blog/admin/templates/blog.html +4 -4
  122. package/plugins/blog/admin/views/blog.js +3 -3
  123. package/plugins/blog/admin/views/categories.js +2 -2
  124. package/plugins/blog/admin/views/comments.js +4 -4
  125. package/plugins/blog/admin/views/post-editor.js +1 -1
  126. package/plugins/blog/plugin.js +2 -2
  127. package/plugins/blog/plugin.json +4 -4
  128. package/plugins/contacts/admin/views/contacts.js +8 -8
  129. package/plugins/contacts/plugin.js +4 -4
  130. package/plugins/contacts/plugin.json +1 -1
  131. package/plugins/invoice/admin/templates/editor.html +6 -6
  132. package/plugins/invoice/admin/templates/index.html +4 -4
  133. package/plugins/invoice/admin/views/editor.js +3 -3
  134. package/plugins/invoice/admin/views/index.js +4 -4
  135. package/plugins/invoice/admin/views/issuers.js +1 -1
  136. package/plugins/invoice/admin/views/party-view.js +3 -3
  137. package/plugins/invoice/admin/views/receivers.js +1 -1
  138. package/plugins/invoice/config.js +1 -1
  139. package/plugins/invoice/plugin.js +1 -1
  140. package/plugins/invoice/plugin.json +4 -4
  141. package/plugins/notes/admin/views/notes.js +4 -4
  142. package/plugins/notes/plugin.js +1 -1
  143. package/plugins/notes/plugin.json +1 -1
  144. package/plugins/site-search/admin/templates/site-search.html +3 -3
  145. package/plugins/site-search/admin/views/site-search.js +1 -1
  146. package/plugins/site-search/config.js +1 -1
  147. package/plugins/site-search/plugin.js +4 -4
  148. package/plugins/site-search/plugin.json +1 -1
  149. package/plugins/surveys/admin/templates/results.html +1 -1
  150. package/plugins/surveys/admin/templates/survey-editor.html +4 -4
  151. package/plugins/surveys/admin/views/audience.js +6 -6
  152. package/plugins/surveys/admin/views/results.js +3 -3
  153. package/plugins/surveys/admin/views/survey-editor.js +1 -1
  154. package/plugins/surveys/admin/views/surveys.js +2 -2
  155. package/plugins/surveys/plugin.js +1 -1
  156. package/plugins/surveys/plugin.json +4 -4
  157. package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
  158. package/plugins/theme-switcher/plugin.json +3 -3
  159. package/plugins/theme-switcher/public/inject-body.html +28 -28
  160. package/plugins/theme-switcher/public/inject-head.html +1 -1
  161. package/plugins/todo/plugin.js +1 -1
  162. package/public/css/forms.css +1 -1
  163. package/public/js/collection-browser.js +1 -1
  164. package/public/js/collection-context.js +1 -1
  165. package/public/js/form-logic-engine.js +1 -1
  166. package/public/js/forms.js +2 -1
  167. package/public/js/site.js +1 -1
  168. package/scripts/build.js +14 -14
  169. package/scripts/create-plugin.js +2 -2
  170. package/scripts/fresh.js +2 -2
  171. package/scripts/gen-instance-secret.js +1 -1
  172. package/scripts/pro.js +12 -12
  173. package/scripts/reset.js +2 -2
  174. package/scripts/setup.js +15 -15
  175. package/scripts/users.js +5 -5
  176. package/scripts/verify-assets.mjs +6 -6
  177. package/server/config.js +1 -1
  178. package/server/middleware/auth.js +253 -253
  179. package/server/middleware/managerAuth.js +3 -3
  180. package/server/routes/api/actions.js +4 -4
  181. package/server/routes/api/api-endpoints.js +7 -7
  182. package/server/routes/api/api-tokens.js +5 -5
  183. package/server/routes/api/auth.js +309 -309
  184. package/server/routes/api/blocks.js +3 -3
  185. package/server/routes/api/collections.js +18 -18
  186. package/server/routes/api/dashboard.js +5 -5
  187. package/server/routes/api/effects.js +2 -2
  188. package/server/routes/api/endpoints-public.js +5 -5
  189. package/server/routes/api/forms.js +47 -47
  190. package/server/routes/api/menu-locations.js +3 -3
  191. package/server/routes/api/menus.js +7 -7
  192. package/server/routes/api/navigation.js +42 -42
  193. package/server/routes/api/notifications.js +2 -2
  194. package/server/routes/api/pages.js +14 -2
  195. package/server/routes/api/plugin-marketplace.js +3 -3
  196. package/server/routes/api/plugins.js +6 -6
  197. package/server/routes/api/projects.js +9 -9
  198. package/server/routes/api/scaffold.js +7 -7
  199. package/server/routes/api/settings.js +6 -6
  200. package/server/routes/api/sidebar.js +1 -1
  201. package/server/routes/api/users.js +5 -5
  202. package/server/routes/api/versions.js +1 -1
  203. package/server/routes/api/views.js +1 -1
  204. package/server/routes/docs-public.js +3 -3
  205. package/server/routes/public.js +14 -14
  206. package/server/server.js +34 -16
  207. package/server/services/actions.js +19 -19
  208. package/server/services/adapters/FileAdapter.js +2 -2
  209. package/server/services/adapters/MongoAdapter.js +5 -5
  210. package/server/services/apiEndpoints.js +15 -15
  211. package/server/services/apiTokens.js +7 -7
  212. package/server/services/blocks.js +13 -13
  213. package/server/services/cache/drivers/MemoryDriver.js +3 -3
  214. package/server/services/cache/drivers/NoneDriver.js +1 -1
  215. package/server/services/cache/index.js +5 -5
  216. package/server/services/collections.js +14 -14
  217. package/server/services/components.js +9 -9
  218. package/server/services/connectionManager.js +4 -4
  219. package/server/services/content.js +25 -2
  220. package/server/services/docs.js +14 -14
  221. package/server/services/email.js +167 -167
  222. package/server/services/filterEngine.js +6 -6
  223. package/server/services/forms.js +18 -10
  224. package/server/services/health.js +1 -1
  225. package/server/services/hooks.js +7 -7
  226. package/server/services/images.js +4 -4
  227. package/server/services/managerClient.js +1 -1
  228. package/server/services/markdown.js +79 -79
  229. package/server/services/menuRender.js +5 -5
  230. package/server/services/menus-migration.js +3 -3
  231. package/server/services/menus.js +31 -31
  232. package/server/services/permissionRegistry.js +1 -1
  233. package/server/services/pluginFiles.js +5 -5
  234. package/server/services/pluginInstaller.js +17 -17
  235. package/server/services/pluginScaffold.js +1 -1
  236. package/server/services/plugins.js +17 -17
  237. package/server/services/presetCollections.js +2 -2
  238. package/server/services/projects.js +35 -26
  239. package/server/services/recipes/contact-list.json +1 -1
  240. package/server/services/recipes/onboarding.json +8 -8
  241. package/server/services/references.js +8 -8
  242. package/server/services/renderer.js +9 -9
  243. package/server/services/roles.js +9 -9
  244. package/server/services/rowAccess.js +10 -10
  245. package/server/services/scaffolder.js +30 -30
  246. package/server/services/sidebar-migration.js +10 -10
  247. package/server/services/sitemap.js +1 -1
  248. package/server/services/userProfiles.js +199 -199
  249. package/server/services/userRoles.js +7 -7
  250. package/server/services/users.js +302 -302
  251. package/server/services/versions.js +3 -3
  252. package/server/services/viewPipeline.js +6 -6
  253. package/server/services/views.js +11 -11
  254. package/server/templates/page.html +5 -5
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Scaffolder — one-shot creator for a working CRUD system from a recipe.
2
+ * Scaffolder - one-shot creator for a working CRUD system from a recipe.
3
3
  *
4
4
  * A "recipe" is a JSON file under `server/services/recipes/` that describes:
5
5
  * - one Collection (schema + initial api access rules)
@@ -14,7 +14,7 @@
14
14
  * and template variable.
15
15
  *
16
16
  * Atomicity: pre-flight check refuses to start if ANY target slug already
17
- * exists — keeps the user out of a partial-create state. If pre-flight passes
17
+ * exists - keeps the user out of a partial-create state. If pre-flight passes
18
18
  * we apply collection → form → actions in order. Action creation requires
19
19
  * MongoDB (Pro feature); when not configured we still complete the
20
20
  * collection + form and report actions as skipped with a warning.
@@ -64,7 +64,7 @@ export async function listRecipes() {
64
64
  options: r.options || []
65
65
  });
66
66
  } catch {
67
- /* skip malformed recipe — don't break the listing */
67
+ /* skip malformed recipe - don't break the listing */
68
68
  }
69
69
  }
70
70
  return recipes;
@@ -101,7 +101,7 @@ function substituteTokens(value, tokens) {
101
101
  if (typeof value === 'string') {
102
102
  return value.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (_m, key) => {
103
103
  // Leave action template variables (entry.*, user.*, now, env.*) untouched
104
- // — those are resolved at action execution time, not at scaffold time.
104
+ // - those are resolved at action execution time, not at scaffold time.
105
105
  if (key.includes('.') || key === 'now') return _m;
106
106
  if (key === 'entry' || key === 'user' || key === 'env') return _m;
107
107
  return tokens[key] != null ? String(tokens[key]) : _m;
@@ -117,18 +117,18 @@ function substituteTokens(value, tokens) {
117
117
  }
118
118
 
119
119
  /**
120
- * Resolve effective options for a recipe — recipe defaults merged with caller
120
+ * Resolve effective options for a recipe - recipe defaults merged with caller
121
121
  * overrides, with non-empty user values winning. Returns a plain `{ name: value }`
122
122
  * map suitable to pass to `substituteTokens`.
123
123
  *
124
124
  * **Default-chaining**: an option's `default` may template-reference earlier
125
- * options via `{{otherOption}}`. This is the "namespacing" affordance — set
125
+ * options via `{{otherOption}}`. This is the "namespacing" affordance - set
126
126
  * one `namespace` option and let `collectionSlug`, `formSlug`, `actionPrefix`
127
127
  * default to `{{namespace}}`, `{{namespace}}-form`, `{{namespace}}` etc. The
128
128
  * recipe author chooses which options participate; users can still override
129
129
  * any individual option to break the chain.
130
130
  *
131
- * Resolution is left-to-right over the options array — so the option being
131
+ * Resolution is left-to-right over the options array - so the option being
132
132
  * referenced MUST appear earlier in the array than the option that references
133
133
  * it. (If forward references become a recurring need we can topologically
134
134
  * sort, but the explicit-order rule keeps recipe files readable.)
@@ -160,7 +160,7 @@ function resolveOptions(recipe, userOverrides = {}) {
160
160
  * Check whether anything the recipe wants to create already exists. Returns a
161
161
  * list of human-readable conflict descriptions; empty array means clear.
162
162
  *
163
- * Action conflict checks are skipped silently when MongoDB isn't configured —
163
+ * Action conflict checks are skipped silently when MongoDB isn't configured -
164
164
  * the apply phase will degrade those into warnings rather than blocking.
165
165
  *
166
166
  * @param {object} resolvedRecipe
@@ -188,12 +188,12 @@ async function preflight(resolvedRecipe) {
188
188
  const existing = await getAction(action.slug);
189
189
  if (existing) conflicts.push(`Action "${action.slug}" already exists`);
190
190
  } catch {
191
- // Mongo unavailable — treat as not-conflicting; the apply step
191
+ // Mongo unavailable - treat as not-conflicting; the apply step
192
192
  // will report actions as skipped instead.
193
193
  }
194
194
  }
195
195
 
196
- // Roles + users are NOT treated as pre-flight conflicts — they're a
196
+ // Roles + users are NOT treated as pre-flight conflicts - they're a
197
197
  // looser kind of resource that frequently already exists (re-running a
198
198
  // recipe, admin-created role, etc.) and where overwriting would lose
199
199
  // data the user cares about (existing role's permissions tweaks, user's
@@ -247,7 +247,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
247
247
  }
248
248
  }
249
249
 
250
- // Project — auto-create if the recipe declares one and it doesn't exist.
250
+ // Project - auto-create if the recipe declares one and it doesn't exist.
251
251
  // Uses the recipe's `namespace` token as the project slug, so a single user
252
252
  // choice ties together every artefact's `meta.project` tag below.
253
253
  let projectSlug = null;
@@ -273,7 +273,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
273
273
 
274
274
  const conflicts = await preflight(resolved);
275
275
  if (conflicts.length) {
276
- const err = new Error(`Cannot apply recipe — conflicts:\n - ${conflicts.join('\n - ')}`);
276
+ const err = new Error(`Cannot apply recipe - conflicts:\n - ${conflicts.join('\n - ')}`);
277
277
  err.statusCode = 409;
278
278
  err.conflicts = conflicts;
279
279
  throw err;
@@ -283,16 +283,16 @@ export async function applyRecipe(recipeSlug, opts = {}) {
283
283
  const skipped = [];
284
284
  const warnings = [];
285
285
 
286
- // 0. Roles — create FIRST so subsequent actions can reference them by name.
286
+ // 0. Roles - create FIRST so subsequent actions can reference them by name.
287
287
  // If a role with the same name already exists (re-run, admin-created),
288
- // we skip silently rather than failing — the role's existence is what
288
+ // we skip silently rather than failing - the role's existence is what
289
289
  // matters for the actions that reference it, not who created it.
290
290
  const existingRoles = getRoleMap();
291
291
  for (const role of (resolved.roles || [])) {
292
292
  if (!role.name) continue;
293
293
  if (existingRoles.has(role.name)) {
294
294
  skipped.push(`role:${role.name}`);
295
- warnings.push(`Role "${role.name}" already exists — using existing definition (recipe-defined permissions not applied)`);
295
+ warnings.push(`Role "${role.name}" already exists - using existing definition (recipe-defined permissions not applied)`);
296
296
  continue;
297
297
  }
298
298
  try {
@@ -310,7 +310,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
310
310
  }
311
311
  }
312
312
 
313
- // 0b. Menus — write each declared menu and, if requested, map it to a slot.
313
+ // 0b. Menus - write each declared menu and, if requested, map it to a slot.
314
314
  for (const menu of (resolved.menus || [])) {
315
315
  if (!menu.slug) continue;
316
316
  try {
@@ -331,7 +331,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
331
331
  let touched = false;
332
332
  for (const slot of menu.locations) {
333
333
  if (map[slot] && map[slot] !== menu.slug && !menu.force) {
334
- warnings.push(`Slot "${slot}" already mapped to "${map[slot]}" — left unchanged (set force:true to overwrite)`);
334
+ warnings.push(`Slot "${slot}" already mapped to "${map[slot]}" - left unchanged (set force:true to overwrite)`);
335
335
  continue;
336
336
  }
337
337
  map[slot] = menu.slug;
@@ -358,11 +358,11 @@ export async function applyRecipe(recipeSlug, opts = {}) {
358
358
  api: resolved.collection.api,
359
359
  // rowAccess is a top-level schema field; createCollection accepts arbitrary keys via spread,
360
360
  // but to be explicit we set it on the schema after create. Simpler: write it via the schema
361
- // file directly — createCollection already writes the schema, so we re-read + patch + write.
361
+ // file directly - createCollection already writes the schema, so we re-read + patch + write.
362
362
  ...(projectSlug && {meta: {project: projectSlug}})
363
363
  });
364
364
 
365
- // Patch in rowAccess (if any) — collections.createCollection doesn't take it directly
365
+ // Patch in rowAccess (if any) - collections.createCollection doesn't take it directly
366
366
  if (resolved.collection.rowAccess) {
367
367
  const {getCollection: get, updateCollection} = await import('./collections.js');
368
368
  const cur = await get(tokens.collectionSlug);
@@ -386,7 +386,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
386
386
  created.form = tokens.formSlug;
387
387
  }
388
388
 
389
- // 2b. Users — recipe-defined seed accounts. Password is mandatory (the
389
+ // 2b. Users - recipe-defined seed accounts. Password is mandatory (the
390
390
  // scaffolder won't invent one); if omitted, the user is reported as
391
391
  // skipped so the admin knows to create them manually. Auto-creating
392
392
  // accounts with a default password would be a meaningful security trap.
@@ -398,14 +398,14 @@ export async function applyRecipe(recipeSlug, opts = {}) {
398
398
  const existing = await getUserByEmail(u.email);
399
399
  if (existing) {
400
400
  skipped.push(`user:${u.email}`);
401
- warnings.push(`User "${u.email}" already exists — left unchanged`);
401
+ warnings.push(`User "${u.email}" already exists - left unchanged`);
402
402
  continue;
403
403
  }
404
404
  } catch { /* no-op */ }
405
405
 
406
406
  if (!u.password) {
407
407
  skipped.push(`user:${u.email}`);
408
- warnings.push(`User "${u.email}" skipped — no password set in recipe. Create the account manually under Users → New.`);
408
+ warnings.push(`User "${u.email}" skipped - no password set in recipe. Create the account manually under Users → New.`);
409
409
  continue;
410
410
  }
411
411
  try {
@@ -423,7 +423,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
423
423
  }
424
424
  }
425
425
 
426
- // 3. Actions — best-effort; skip with warning when Mongo isn't available
426
+ // 3. Actions - best-effort; skip with warning when Mongo isn't available
427
427
  for (const action of (resolved.actions || [])) {
428
428
  if (!action.slug) continue;
429
429
  try {
@@ -441,7 +441,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
441
441
  created.actions.push(a.slug);
442
442
  } catch (err) {
443
443
  const reason = err.message.includes('MongoDB') || err.message.includes('Mongo')
444
- ? `MongoDB not configured — action "${action.slug}" skipped (Pro feature)`
444
+ ? `MongoDB not configured - action "${action.slug}" skipped (Pro feature)`
445
445
  : `Action "${action.slug}" failed: ${err.message}`;
446
446
  skipped.push(action.slug);
447
447
  warnings.push(reason);
@@ -449,17 +449,17 @@ export async function applyRecipe(recipeSlug, opts = {}) {
449
449
  }
450
450
 
451
451
  // Wire the form's actionSlug to the first created action if the recipe
452
- // wanted it — convenient for "submit this form, fire that action" patterns.
452
+ // wanted it - convenient for "submit this form, fire that action" patterns.
453
453
  if (created.form && created.actions.length && resolved.form?.settings?.actionSlug === '') {
454
454
  try {
455
455
  const formDef = await readForm(created.form);
456
456
  formDef.settings = formDef.settings || {};
457
457
  formDef.settings.actionSlug = created.actions[0];
458
458
  await writeForm(created.form, formDef);
459
- } catch { /* non-fatal — admin can wire it manually */ }
459
+ } catch { /* non-fatal - admin can wire it manually */ }
460
460
  }
461
461
 
462
- // API tokens — generated at apply time; the plaintext rides ONCE in
462
+ // API tokens - generated at apply time; the plaintext rides ONCE in
463
463
  // `created.apiTokens[].token` (only a hash is stored). Idempotent:
464
464
  // re-applying never re-issues an existing name+project token, so the
465
465
  // credential the user already deployed survives recipe re-runs.
@@ -471,7 +471,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
471
471
  if (!t.name) continue;
472
472
  if (await findTokenByName(t.name, tokenProject)) {
473
473
  skipped.push(`apiToken:${t.name}`);
474
- warnings.push(`API token "${t.name}" already exists for project "${tokenProject}" — left unchanged (token value NOT re-issued)`);
474
+ warnings.push(`API token "${t.name}" already exists for project "${tokenProject}" - left unchanged (token value NOT re-issued)`);
475
475
  continue;
476
476
  }
477
477
  try {
@@ -489,7 +489,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
489
489
  }
490
490
  }
491
491
 
492
- // Custom API endpoints — declared as definition objects (path, collection,
492
+ // Custom API endpoints - declared as definition objects (path, collection,
493
493
  // filter, ...). Idempotent: an existing (project, path-shape) match is
494
494
  // skipped so re-applying a recipe never clobbers a tuned definition.
495
495
  const endpointDecls = resolved.apiEndpoints ? [].concat(resolved.apiEndpoints) : [];
@@ -500,7 +500,7 @@ export async function applyRecipe(recipeSlug, opts = {}) {
500
500
  if (!ep.path) continue;
501
501
  if (await findEndpointByPath(epProject, ep.path)) {
502
502
  skipped.push(`apiEndpoint:${ep.path}`);
503
- warnings.push(`API endpoint "${ep.path}" already exists for project "${epProject}" — left unchanged`);
503
+ warnings.push(`API endpoint "${ep.path}" already exists for project "${epProject}" - left unchanged`);
504
504
  continue;
505
505
  }
506
506
  try {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Admin sidebar auto-migration.
3
3
  *
4
- * Runs once on server boot. Idempotent — guarded by the existence of
4
+ * Runs once on server boot. Idempotent - guarded by the existence of
5
5
  * config/menus/admin-sidebar.json. Seeds the admin sidebar menu with the
6
6
  * standard tree (Overview, Projects, Content, Data, System, Documentation)
7
7
  * and patches config/menu-locations.json to map the `admin-sidebar` slot.
@@ -148,7 +148,7 @@ async function readJson(p) {
148
148
 
149
149
  async function writeJson(p, data) {
150
150
  await fs.writeFile(p, JSON.stringify(data, null, 2) + '\n', 'utf8');
151
- // Menu files written outside the menus service — drop its cached index so
151
+ // Menu files written outside the menus service - drop its cached index so
152
152
  // binding resolution sees the seeded sidebar menu.
153
153
  invalidateMenuIndex();
154
154
  }
@@ -180,7 +180,7 @@ export async function runMigration(opts = {}) {
180
180
 
181
181
  let locations = {};
182
182
  if (await exists(locPath)) {
183
- try { locations = await readJson(locPath); } catch { /* malformed — start fresh */ }
183
+ try { locations = await readJson(locPath); } catch { /* malformed - start fresh */ }
184
184
  }
185
185
  locations['admin-sidebar'] = 'admin-sidebar';
186
186
  await writeJson(locPath, locations);
@@ -193,8 +193,8 @@ export async function runMigration(opts = {}) {
193
193
  * Append-if-absent: ensure a single item exists in the persisted admin
194
194
  * sidebar menu. Lets updates surface new admin pages on EXISTING installs
195
195
  * (the seed above only reaches fresh ones) without clobbering admin edits:
196
- * if any node in the tree already has the item's url — even moved, renamed,
197
- * or hidden — this is a no-op.
196
+ * if any node in the tree already has the item's url - even moved, renamed,
197
+ * or hidden - this is a no-op.
198
198
  *
199
199
  * Returns `{ensured: boolean, reason?: string}`.
200
200
  *
@@ -237,13 +237,13 @@ export async function ensureSidebarItem({groupText, item}, opts = {}) {
237
237
  * per-topic submenus. Idempotent and non-clobbering:
238
238
  *
239
239
  * - If the persisted menu has no "Documentation" group, do nothing (the admin
240
- * may have removed it deliberately — don't resurrect it).
241
- * - Convert each flat leaf — "Usage" (`#/documentation`), "Tutorials"
242
- * (`#/tutorials`) and "API Reference" (`#/api-reference`) — into its
240
+ * may have removed it deliberately - don't resurrect it).
241
+ * - Convert each flat leaf - "Usage" (`#/documentation`), "Tutorials"
242
+ * (`#/tutorials`) and "API Reference" (`#/api-reference`) - into its
243
243
  * submenu, replacing the node in place so its position is preserved. A leaf
244
244
  * that the admin already turned into a parent, or whose new pages already
245
245
  * appear anywhere in the group, is left untouched.
246
- * - Add the Components submenu only if `#/docs/components` isn't present yet —
246
+ * - Add the Components submenu only if `#/docs/components` isn't present yet -
247
247
  * inserted before "API Reference" when that node exists.
248
248
  *
249
249
  * Returns `{updated: boolean, reason?: string}`.
@@ -290,7 +290,7 @@ export async function ensureDocumentationSubmenus(opts = {}) {
290
290
  changed = true;
291
291
  }
292
292
 
293
- // Components submenu — add if absent, before API Reference.
293
+ // Components submenu - add if absent, before API Reference.
294
294
  if (!hasUrl(docGroup.items, '#/docs/components')) {
295
295
  const apiIdx = findByText('API Reference');
296
296
  const node = DOC_COMPONENTS_SUBMENU();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Sitemap Service
3
3
  * Builds sitemap.xml from the page list. Filter logic lives in
4
- * shouldIncludeInSitemap() — see the TODO below.
4
+ * shouldIncludeInSitemap() - see the TODO below.
5
5
  *
6
6
  * Cache: callers should wrap generate() with the 'sitemap' tag so it
7
7
  * invalidates whenever a page is created, updated, renamed, or deleted.