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.
- package/CLAUDE.md +53 -53
- package/README.md +27 -27
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +2 -2
- package/admin/js/app.js +1 -1
- package/admin/js/lib/card-builder.js +2 -2
- package/admin/js/lib/crud-tutorial.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/image-editor.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +2 -2
- package/admin/js/lib/project-context.js +1 -1
- package/admin/js/lib/project-quick-create.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -1
- package/admin/js/lib/server-restart.js +1 -1
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/simple-editor.js +1 -1
- package/admin/js/lib/themes.js +1 -1
- package/admin/js/lib/timeline-builder.js +1 -1
- package/admin/js/templates/action-editor.html +8 -8
- package/admin/js/templates/api-endpoint-editor.html +7 -7
- package/admin/js/templates/api-endpoints.html +1 -1
- package/admin/js/templates/api-reference.html +1 -1
- package/admin/js/templates/block-editor.html +6 -6
- package/admin/js/templates/collection-editor.html +1 -1
- package/admin/js/templates/component-editor.html +2 -2
- package/admin/js/templates/dashboard/cache.html +3 -3
- package/admin/js/templates/dashboard/journeys.html +3 -3
- package/admin/js/templates/dashboard/kpi-strip.html +3 -3
- package/admin/js/templates/dashboard/top-pages.html +1 -1
- package/admin/js/templates/dashboard/traffic-chart.html +1 -1
- package/admin/js/templates/docs/api-actions.html +1 -1
- package/admin/js/templates/docs/api-collections.html +4 -4
- package/admin/js/templates/docs/api-pages.html +2 -2
- package/admin/js/templates/docs/api-plugins.html +1 -1
- package/admin/js/templates/docs/api-settings.html +2 -2
- package/admin/js/templates/docs/api-users.html +1 -1
- package/admin/js/templates/docs/components-howto.html +4 -4
- package/admin/js/templates/docs/components-reference.html +21 -21
- package/admin/js/templates/docs/components-rules.html +14 -14
- package/admin/js/templates/docs/components-walkthrough.html +13 -13
- package/admin/js/templates/docs/tutorial-crud.html +49 -49
- package/admin/js/templates/docs/tutorial-forms.html +4 -4
- package/admin/js/templates/docs/tutorial-plugin.html +10 -10
- package/admin/js/templates/docs/usage-actions.html +6 -6
- package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
- package/admin/js/templates/docs/usage-dconfig.html +4 -4
- package/admin/js/templates/docs/usage-navigation.html +1 -1
- package/admin/js/templates/docs/usage-pages.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +24 -24
- package/admin/js/templates/docs/usage-users-roles.html +3 -3
- package/admin/js/templates/docs/usage-views.html +4 -4
- package/admin/js/templates/documentation.html +1 -1
- package/admin/js/templates/effects.html +752 -752
- package/admin/js/templates/form-editor.html +5 -5
- package/admin/js/templates/forms.html +17 -17
- package/admin/js/templates/login.html +5 -5
- package/admin/js/templates/menu-editor.html +10 -10
- package/admin/js/templates/my-profile.html +17 -17
- package/admin/js/templates/navigation.html +11 -11
- package/admin/js/templates/notifications.html +1 -1
- package/admin/js/templates/page-editor.html +4 -4
- package/admin/js/templates/pro-docs.html +14 -14
- package/admin/js/templates/role-editor.html +70 -70
- package/admin/js/templates/roles.html +10 -10
- package/admin/js/templates/settings.html +10 -10
- package/admin/js/templates/tutorials.html +3 -3
- package/admin/js/templates/view-editor.html +15 -15
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoint-editor.js +2 -2
- package/admin/js/views/api-endpoints.js +2 -2
- package/admin/js/views/api-tokens.js +3 -3
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +1 -1
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/component-editor.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/dashboard/widgets/cache.js +1 -1
- package/admin/js/views/dashboard/widgets/journeys.js +1 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +3 -3
- package/admin/js/views/menu-locations.js +1 -1
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/navigation.js +8 -8
- package/admin/js/views/page-editor.js +40 -40
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/plugin-code.js +2 -2
- package/admin/js/views/plugins.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +1 -1
- package/admin/js/views/roles.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/bin/cli.js +8 -8
- package/bin/lib/config-merge.js +44 -44
- package/bin/update.js +16 -16
- package/config/plugins.json +1 -1
- package/package.json +2 -2
- package/plugins/_template/admin/views/index.js +1 -1
- package/plugins/_template/config.js +1 -1
- package/plugins/_template/plugin.js +1 -1
- package/plugins/analytics/admin/templates/analytics.html +3 -3
- package/plugins/analytics/admin/views/analytics.js +1 -1
- package/plugins/analytics/config.js +1 -1
- package/plugins/analytics/plugin.js +4 -4
- package/plugins/analytics/plugin.json +1 -1
- package/plugins/analytics/public/inject-body.html +2 -2
- package/plugins/analytics/public/inject-head.html +1 -1
- package/plugins/blog/admin/templates/blog.html +4 -4
- package/plugins/blog/admin/views/blog.js +3 -3
- package/plugins/blog/admin/views/categories.js +2 -2
- package/plugins/blog/admin/views/comments.js +4 -4
- package/plugins/blog/admin/views/post-editor.js +1 -1
- package/plugins/blog/plugin.js +2 -2
- package/plugins/blog/plugin.json +4 -4
- package/plugins/contacts/admin/views/contacts.js +8 -8
- package/plugins/contacts/plugin.js +4 -4
- package/plugins/contacts/plugin.json +1 -1
- package/plugins/invoice/admin/templates/editor.html +6 -6
- package/plugins/invoice/admin/templates/index.html +4 -4
- package/plugins/invoice/admin/views/editor.js +3 -3
- package/plugins/invoice/admin/views/index.js +4 -4
- package/plugins/invoice/admin/views/issuers.js +1 -1
- package/plugins/invoice/admin/views/party-view.js +3 -3
- package/plugins/invoice/admin/views/receivers.js +1 -1
- package/plugins/invoice/config.js +1 -1
- package/plugins/invoice/plugin.js +1 -1
- package/plugins/invoice/plugin.json +4 -4
- package/plugins/notes/admin/views/notes.js +4 -4
- package/plugins/notes/plugin.js +1 -1
- package/plugins/notes/plugin.json +1 -1
- package/plugins/site-search/admin/templates/site-search.html +3 -3
- package/plugins/site-search/admin/views/site-search.js +1 -1
- package/plugins/site-search/config.js +1 -1
- package/plugins/site-search/plugin.js +4 -4
- package/plugins/site-search/plugin.json +1 -1
- package/plugins/surveys/admin/templates/results.html +1 -1
- package/plugins/surveys/admin/templates/survey-editor.html +4 -4
- package/plugins/surveys/admin/views/audience.js +6 -6
- package/plugins/surveys/admin/views/results.js +3 -3
- package/plugins/surveys/admin/views/survey-editor.js +1 -1
- package/plugins/surveys/admin/views/surveys.js +2 -2
- package/plugins/surveys/plugin.js +1 -1
- package/plugins/surveys/plugin.json +4 -4
- package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
- package/plugins/theme-switcher/plugin.json +3 -3
- package/plugins/theme-switcher/public/inject-body.html +28 -28
- package/plugins/theme-switcher/public/inject-head.html +1 -1
- package/plugins/todo/plugin.js +1 -1
- package/public/css/forms.css +1 -1
- package/public/js/collection-browser.js +1 -1
- package/public/js/collection-context.js +1 -1
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -1
- package/public/js/site.js +1 -1
- package/scripts/build.js +14 -14
- package/scripts/create-plugin.js +2 -2
- package/scripts/fresh.js +2 -2
- package/scripts/gen-instance-secret.js +1 -1
- package/scripts/pro.js +12 -12
- package/scripts/reset.js +2 -2
- package/scripts/setup.js +15 -15
- package/scripts/users.js +5 -5
- package/scripts/verify-assets.mjs +6 -6
- package/server/config.js +1 -1
- package/server/middleware/auth.js +253 -253
- package/server/middleware/managerAuth.js +3 -3
- package/server/routes/api/actions.js +4 -4
- package/server/routes/api/api-endpoints.js +7 -7
- package/server/routes/api/api-tokens.js +5 -5
- package/server/routes/api/auth.js +309 -309
- package/server/routes/api/blocks.js +3 -3
- package/server/routes/api/collections.js +18 -18
- package/server/routes/api/dashboard.js +5 -5
- package/server/routes/api/effects.js +2 -2
- package/server/routes/api/endpoints-public.js +5 -5
- package/server/routes/api/forms.js +47 -47
- package/server/routes/api/menu-locations.js +3 -3
- package/server/routes/api/menus.js +7 -7
- package/server/routes/api/navigation.js +42 -42
- package/server/routes/api/notifications.js +2 -2
- package/server/routes/api/pages.js +14 -2
- package/server/routes/api/plugin-marketplace.js +3 -3
- package/server/routes/api/plugins.js +6 -6
- package/server/routes/api/projects.js +9 -9
- package/server/routes/api/scaffold.js +7 -7
- package/server/routes/api/settings.js +6 -6
- package/server/routes/api/sidebar.js +1 -1
- package/server/routes/api/users.js +5 -5
- package/server/routes/api/versions.js +1 -1
- package/server/routes/api/views.js +1 -1
- package/server/routes/docs-public.js +3 -3
- package/server/routes/public.js +14 -14
- package/server/server.js +34 -16
- package/server/services/actions.js +19 -19
- package/server/services/adapters/FileAdapter.js +2 -2
- package/server/services/adapters/MongoAdapter.js +5 -5
- package/server/services/apiEndpoints.js +15 -15
- package/server/services/apiTokens.js +7 -7
- package/server/services/blocks.js +13 -13
- package/server/services/cache/drivers/MemoryDriver.js +3 -3
- package/server/services/cache/drivers/NoneDriver.js +1 -1
- package/server/services/cache/index.js +5 -5
- package/server/services/collections.js +14 -14
- package/server/services/components.js +9 -9
- package/server/services/connectionManager.js +4 -4
- package/server/services/content.js +25 -2
- package/server/services/docs.js +14 -14
- package/server/services/email.js +167 -167
- package/server/services/filterEngine.js +6 -6
- package/server/services/forms.js +18 -10
- package/server/services/health.js +1 -1
- package/server/services/hooks.js +7 -7
- package/server/services/images.js +4 -4
- package/server/services/managerClient.js +1 -1
- package/server/services/markdown.js +79 -79
- package/server/services/menuRender.js +5 -5
- package/server/services/menus-migration.js +3 -3
- package/server/services/menus.js +31 -31
- package/server/services/permissionRegistry.js +1 -1
- package/server/services/pluginFiles.js +5 -5
- package/server/services/pluginInstaller.js +17 -17
- package/server/services/pluginScaffold.js +1 -1
- package/server/services/plugins.js +17 -17
- package/server/services/presetCollections.js +2 -2
- package/server/services/projects.js +35 -26
- package/server/services/recipes/contact-list.json +1 -1
- package/server/services/recipes/onboarding.json +8 -8
- package/server/services/references.js +8 -8
- package/server/services/renderer.js +9 -9
- package/server/services/roles.js +9 -9
- package/server/services/rowAccess.js +10 -10
- package/server/services/scaffolder.js +30 -30
- package/server/services/sidebar-migration.js +10 -10
- package/server/services/sitemap.js +1 -1
- package/server/services/userProfiles.js +199 -199
- package/server/services/userRoles.js +7 -7
- package/server/services/users.js +302 -302
- package/server/services/versions.js +3 -3
- package/server/services/viewPipeline.js +6 -6
- package/server/services/views.js +11 -11
- package/server/templates/page.html +5 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scaffolder
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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]}"
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
459
|
+
} catch { /* non-fatal - admin can wire it manually */ }
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
// API tokens
|
|
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}"
|
|
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
|
|
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}"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
197
|
-
* or hidden
|
|
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
|
|
241
|
-
* - Convert each flat leaf
|
|
242
|
-
* (`#/tutorials`) and "API Reference" (`#/api-reference`)
|
|
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
|
|
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()
|
|
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.
|