domma-cms 0.43.1 → 0.44.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.
@@ -1,17 +1,17 @@
1
- <div class="view-header">
2
- <h1><span data-icon="layout"></span> Forms</h1>
3
- <div style="display:flex;gap:.5rem;">
4
- <button id="test-email-btn" class="btn btn-ghost btn-sm">
5
- <span data-icon="mail"></span> Test Email
6
- </button>
7
- <button id="create-form-btn" class="btn btn-primary">
8
- <span data-icon="plus"></span> Create Form
9
- </button>
10
- </div>
11
- </div>
12
-
13
- <div class="card">
14
- <div class="card-body">
15
- <div id="forms-table"></div>
16
- </div>
17
- </div>
1
+ <div class="view-header">
2
+ <h1><span data-icon="layout"></span> Forms</h1>
3
+ <div style="display:flex;gap:.5rem;">
4
+ <button id="test-email-btn" class="btn btn-ghost btn-sm">
5
+ <span data-icon="mail"></span> Test Email
6
+ </button>
7
+ <button id="create-form-btn" class="btn btn-primary">
8
+ <span data-icon="plus"></span> Create Form
9
+ </button>
10
+ </div>
11
+ </div>
12
+
13
+ <div class="card">
14
+ <div class="card-body">
15
+ <div id="forms-table"></div>
16
+ </div>
17
+ </div>
@@ -1,17 +1,17 @@
1
- <div class="view-header">
2
- <h1><span data-icon="user"></span> My Profile</h1>
3
- </div>
4
-
5
- <div class="card">
6
- <div class="card-header"><h2>Account</h2></div>
7
- <div class="card-body">
8
- <div id="account-form-container"></div>
9
- </div>
10
- </div>
11
-
12
- <div class="card mt-4" id="profile-card" style="display:none;">
13
- <div class="card-header"><h2>Profile</h2></div>
14
- <div class="card-body">
15
- <div id="profile-form-container"></div>
16
- </div>
17
- </div>
1
+ <div class="view-header">
2
+ <h1><span data-icon="user"></span> My Profile</h1>
3
+ </div>
4
+
5
+ <div class="card">
6
+ <div class="card-header"><h2>Account</h2></div>
7
+ <div class="card-body">
8
+ <div id="account-form-container"></div>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="card mt-4" id="profile-card" style="display:none;">
13
+ <div class="card-header"><h2>Profile</h2></div>
14
+ <div class="card-body">
15
+ <div id="profile-form-container"></div>
16
+ </div>
17
+ </div>
@@ -1,70 +1,70 @@
1
- <div class="view-header">
2
- <h1><span data-icon="shield"></span> <span id="role-title">Edit Role</span></h1>
3
- <div>
4
- <a href="#/roles" class="btn btn-secondary"><span data-icon="arrow-left"></span> Back</a>
5
- <button id="btn-save-role" class="btn btn-primary"><span data-icon="save"></span> Save</button>
6
- </div>
7
- </div>
8
-
9
- <div class="tabs" id="role-tabs">
10
- <div class="tab-list">
11
- <button class="tab-item active">General</button>
12
- <button class="tab-item">Permissions</button>
13
- </div>
14
- <div class="tab-content">
15
- <!-- General Tab -->
16
- <div class="tab-panel active" id="panel-general">
17
- <div class="card">
18
- <div class="card-body">
19
- <div class="row">
20
- <div class="col-6">
21
- <div class="mb-3">
22
- <label class="form-label">Name (slug)</label>
23
- <input id="role-name" type="text" class="form-input" placeholder="e.g. moderator">
24
- <p class="form-hint">Lowercase identifier. Cannot be changed for the root admin
25
- role.</p>
26
- </div>
27
- </div>
28
- <div class="col-6">
29
- <div class="mb-3">
30
- <label class="form-label">Display Label</label>
31
- <input id="role-label" type="text" class="form-input" placeholder="e.g. Moderator">
32
- </div>
33
- </div>
34
- </div>
35
- <div class="row">
36
- <div class="col-6">
37
- <div class="mb-3">
38
- <label class="form-label">Level</label>
39
- <input id="role-level" type="number" class="form-input" min="0">
40
- <p class="form-hint">Lower = more privileged. 0 is reserved for the root admin.</p>
41
- </div>
42
- </div>
43
- <div class="col-6">
44
- <div class="mb-3">
45
- <label class="form-label">Badge Colour</label>
46
- <select id="role-badge" class="form-input"></select>
47
- </div>
48
- </div>
49
- </div>
50
- <div class="row">
51
- <div class="col-6">
52
- <div class="mb-3">
53
- <label class="form-label">Project</label>
54
- <select id="role-project" class="form-input">
55
- <option value="">— none —</option>
56
- </select>
57
- <p class="form-hint">Tag this role to a project for grouping.</p>
58
- </div>
59
- </div>
60
- </div>
61
- </div>
62
- </div>
63
- </div>
64
-
65
- <!-- Permissions Tab -->
66
- <div class="tab-panel" id="panel-permissions">
67
- <div id="permissions-container"></div>
68
- </div>
69
- </div>
70
- </div>
1
+ <div class="view-header">
2
+ <h1><span data-icon="shield"></span> <span id="role-title">Edit Role</span></h1>
3
+ <div>
4
+ <a href="#/roles" class="btn btn-secondary"><span data-icon="arrow-left"></span> Back</a>
5
+ <button id="btn-save-role" class="btn btn-primary"><span data-icon="save"></span> Save</button>
6
+ </div>
7
+ </div>
8
+
9
+ <div class="tabs" id="role-tabs">
10
+ <div class="tab-list">
11
+ <button class="tab-item active">General</button>
12
+ <button class="tab-item">Permissions</button>
13
+ </div>
14
+ <div class="tab-content">
15
+ <!-- General Tab -->
16
+ <div class="tab-panel active" id="panel-general">
17
+ <div class="card">
18
+ <div class="card-body">
19
+ <div class="row">
20
+ <div class="col-6">
21
+ <div class="mb-3">
22
+ <label class="form-label">Name (slug)</label>
23
+ <input id="role-name" type="text" class="form-input" placeholder="e.g. moderator">
24
+ <p class="form-hint">Lowercase identifier. Cannot be changed for the root admin
25
+ role.</p>
26
+ </div>
27
+ </div>
28
+ <div class="col-6">
29
+ <div class="mb-3">
30
+ <label class="form-label">Display Label</label>
31
+ <input id="role-label" type="text" class="form-input" placeholder="e.g. Moderator">
32
+ </div>
33
+ </div>
34
+ </div>
35
+ <div class="row">
36
+ <div class="col-6">
37
+ <div class="mb-3">
38
+ <label class="form-label">Level</label>
39
+ <input id="role-level" type="number" class="form-input" min="0">
40
+ <p class="form-hint">Lower = more privileged. 0 is reserved for the root admin.</p>
41
+ </div>
42
+ </div>
43
+ <div class="col-6">
44
+ <div class="mb-3">
45
+ <label class="form-label">Badge Colour</label>
46
+ <select id="role-badge" class="form-input"></select>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ <div class="row">
51
+ <div class="col-6">
52
+ <div class="mb-3">
53
+ <label class="form-label">Project</label>
54
+ <select id="role-project" class="form-input">
55
+ <option value="">— none —</option>
56
+ </select>
57
+ <p class="form-hint">Tag this role to a project for grouping.</p>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <!-- Permissions Tab -->
66
+ <div class="tab-panel" id="panel-permissions">
67
+ <div id="permissions-container"></div>
68
+ </div>
69
+ </div>
70
+ </div>
@@ -1,10 +1,10 @@
1
- <div class="view-header">
2
- <h1><span data-icon="shield"></span> Roles &amp; Permissions</h1>
3
- <button id="btn-add-role" class="btn btn-primary"><span data-icon="plus"></span> Add Role</button>
4
- </div>
5
-
6
- <div class="card">
7
- <div class="card-body">
8
- <div id="roles-table"></div>
9
- </div>
10
- </div>
1
+ <div class="view-header">
2
+ <h1><span data-icon="shield"></span> Roles &amp; Permissions</h1>
3
+ <button id="btn-add-role" class="btn btn-primary"><span data-icon="plus"></span> Add Role</button>
4
+ </div>
5
+
6
+ <div class="card">
7
+ <div class="card-body">
8
+ <div id="roles-table"></div>
9
+ </div>
10
+ </div>
@@ -1,44 +1,44 @@
1
- /**
2
- * Domma CMS — Config Merge Utility
3
- * Merges new keys from an upstream config into an existing user config,
4
- * without ever overwriting values the user already has.
5
- */
6
-
7
- /**
8
- * Deep-merge new keys from `upstream` into `existing`.
9
- * Existing values are never modified — only missing keys are added.
10
- *
11
- * @param {object} existing - The user's current config object
12
- * @param {object} upstream - The upstream (new version) config object
13
- * @param {string} [_prefix] - Internal: key path prefix for reporting
14
- * @returns {{ merged: object, added: string[] }} Merged object + list of added key paths
15
- */
16
- export function deepMergeNewKeys(existing, upstream, _prefix = '') {
17
- const merged = {...existing};
18
- const added = [];
19
-
20
- for (const [key, upstreamVal] of Object.entries(upstream)) {
21
- const fullKey = _prefix ? `${_prefix}.${key}` : key;
22
-
23
- if (!(key in existing)) {
24
- // Key is entirely missing — add it wholesale
25
- merged[key] = upstreamVal;
26
- added.push(fullKey);
27
- } else if (
28
- upstreamVal !== null &&
29
- typeof upstreamVal === 'object' &&
30
- !Array.isArray(upstreamVal) &&
31
- typeof existing[key] === 'object' &&
32
- existing[key] !== null &&
33
- !Array.isArray(existing[key])
34
- ) {
35
- // Both sides are plain objects — recurse
36
- const child = deepMergeNewKeys(existing[key], upstreamVal, fullKey);
37
- merged[key] = child.merged;
38
- added.push(...child.added);
39
- }
40
- // Otherwise: existing value wins — no action
41
- }
42
-
43
- return {merged, added};
44
- }
1
+ /**
2
+ * Domma CMS — Config Merge Utility
3
+ * Merges new keys from an upstream config into an existing user config,
4
+ * without ever overwriting values the user already has.
5
+ */
6
+
7
+ /**
8
+ * Deep-merge new keys from `upstream` into `existing`.
9
+ * Existing values are never modified — only missing keys are added.
10
+ *
11
+ * @param {object} existing - The user's current config object
12
+ * @param {object} upstream - The upstream (new version) config object
13
+ * @param {string} [_prefix] - Internal: key path prefix for reporting
14
+ * @returns {{ merged: object, added: string[] }} Merged object + list of added key paths
15
+ */
16
+ export function deepMergeNewKeys(existing, upstream, _prefix = '') {
17
+ const merged = {...existing};
18
+ const added = [];
19
+
20
+ for (const [key, upstreamVal] of Object.entries(upstream)) {
21
+ const fullKey = _prefix ? `${_prefix}.${key}` : key;
22
+
23
+ if (!(key in existing)) {
24
+ // Key is entirely missing — add it wholesale
25
+ merged[key] = upstreamVal;
26
+ added.push(fullKey);
27
+ } else if (
28
+ upstreamVal !== null &&
29
+ typeof upstreamVal === 'object' &&
30
+ !Array.isArray(upstreamVal) &&
31
+ typeof existing[key] === 'object' &&
32
+ existing[key] !== null &&
33
+ !Array.isArray(existing[key])
34
+ ) {
35
+ // Both sides are plain objects — recurse
36
+ const child = deepMergeNewKeys(existing[key], upstreamVal, fullKey);
37
+ merged[key] = child.merged;
38
+ added.push(...child.added);
39
+ }
40
+ // Otherwise: existing value wins — no action
41
+ }
42
+
43
+ return {merged, added};
44
+ }
@@ -20,7 +20,7 @@
20
20
  "settings": {}
21
21
  },
22
22
  "blog": {
23
- "enabled": false,
23
+ "enabled": true,
24
24
  "bundled": false,
25
25
  "settings": {}
26
26
  },
package/config/site.json CHANGED
@@ -1,88 +1,88 @@
1
1
  {
2
- "title": "My CMS Site",
3
- "url": "http://domma-cms.local:4096",
4
- "tagline": "This is the Domma CMS",
5
- "theme": "silver-dark",
6
- "seo": {
7
- "defaultTitle": "My Bloggy Thing",
8
- "titleSeparator": " | ",
9
- "defaultDescription": "A site built with Domma CMS"
10
- },
11
- "footer": {
12
- "copyright": "© My Site. All rights reserved.",
13
- "links": []
14
- },
15
- "social": {
16
- "twitter": "",
17
- "facebook": "",
18
- "instagram": "",
19
- "linkedin": "",
20
- "github": "",
21
- "youtube": ""
22
- },
23
- "smtp": {
24
- "host": "192.168.1.27",
25
- "port": 1025,
26
- "user": "",
27
- "pass": "",
28
- "secure": false,
29
- "fromAddress": "",
30
- "fromName": ""
31
- },
32
- "adminBrand": {
33
- "title": "Domma CMS"
34
- },
35
- "fontFamily": "Nunito",
36
- "fontSize": 18,
37
- "autoTheme": {
38
- "enabled": false,
39
- "dayTheme": "charcoal-light",
40
- "nightTheme": "charcoal-dark",
41
- "dayStart": "07:00",
42
- "nightStart": "19:00"
43
- },
44
- "adminTheme": "silver-dark",
45
- "layoutOptions": {
46
- "spacerSize": 40
47
- },
48
- "backToTop": {
49
- "enabled": true,
50
- "scrollThreshold": 300,
51
- "position": "bottom-right",
52
- "offset": 32,
53
- "bottomOffset": 32,
54
- "label": "",
55
- "smooth": true
56
- },
57
- "cookieConsent": {
58
- "enabled": true,
59
- "message": "",
60
- "acceptAllText": "Accept All",
61
- "rejectAllText": "Reject All",
62
- "customizeText": "Customize",
63
- "savePreferencesText": "Save Preferences",
64
- "privacyPolicyText": "Privacy Policy",
65
- "privacyPolicyUrl": "",
66
- "cookiePolicyText": "Cookie Policy",
67
- "cookiePolicyUrl": "",
68
- "position": "bottom",
69
- "layout": "bar",
70
- "theme": "dark",
71
- "showFunctional": true,
72
- "showAnalytics": true,
73
- "showMarketing": true,
74
- "consentVersion": "1.0"
75
- },
76
- "breadcrumbs": {
77
- "enabled": false,
78
- "homeLabel": "Home",
79
- "position": "TR",
80
- "offsetX": 64,
81
- "offsetY": 64
82
- },
83
- "brand": {
84
- "text": "My Bloggy Thing",
85
- "logo": null,
86
- "url": "/"
87
- }
2
+ "title": "My CMS Site",
3
+ "url": "http://localhost:4096",
4
+ "tagline": "This is the Domma CMS",
5
+ "theme": "silver-dark",
6
+ "seo": {
7
+ "defaultTitle": "My Bloggy Thing",
8
+ "titleSeparator": " | ",
9
+ "defaultDescription": "A site built with Domma CMS"
10
+ },
11
+ "footer": {
12
+ "copyright": "© My Site. All rights reserved.",
13
+ "links": []
14
+ },
15
+ "social": {
16
+ "twitter": "",
17
+ "facebook": "",
18
+ "instagram": "",
19
+ "linkedin": "",
20
+ "github": "",
21
+ "youtube": ""
22
+ },
23
+ "smtp": {
24
+ "host": "",
25
+ "port": 587,
26
+ "user": "",
27
+ "pass": "",
28
+ "secure": false,
29
+ "fromAddress": "",
30
+ "fromName": ""
31
+ },
32
+ "adminBrand": {
33
+ "title": "Domma CMS"
34
+ },
35
+ "fontFamily": "Nunito",
36
+ "fontSize": 18,
37
+ "autoTheme": {
38
+ "enabled": false,
39
+ "dayTheme": "charcoal-light",
40
+ "nightTheme": "charcoal-dark",
41
+ "dayStart": "07:00",
42
+ "nightStart": "19:00"
43
+ },
44
+ "adminTheme": "silver-dark",
45
+ "layoutOptions": {
46
+ "spacerSize": 40
47
+ },
48
+ "backToTop": {
49
+ "enabled": true,
50
+ "scrollThreshold": 300,
51
+ "position": "bottom-right",
52
+ "offset": 32,
53
+ "bottomOffset": 32,
54
+ "label": "",
55
+ "smooth": true
56
+ },
57
+ "cookieConsent": {
58
+ "enabled": true,
59
+ "message": "",
60
+ "acceptAllText": "Accept All",
61
+ "rejectAllText": "Reject All",
62
+ "customizeText": "Customize",
63
+ "savePreferencesText": "Save Preferences",
64
+ "privacyPolicyText": "Privacy Policy",
65
+ "privacyPolicyUrl": "",
66
+ "cookiePolicyText": "Cookie Policy",
67
+ "cookiePolicyUrl": "",
68
+ "position": "bottom",
69
+ "layout": "bar",
70
+ "theme": "dark",
71
+ "showFunctional": true,
72
+ "showAnalytics": true,
73
+ "showMarketing": true,
74
+ "consentVersion": "1.0"
75
+ },
76
+ "breadcrumbs": {
77
+ "enabled": false,
78
+ "homeLabel": "Home",
79
+ "position": "TR",
80
+ "offsetX": 64,
81
+ "offsetY": 64
82
+ },
83
+ "brand": {
84
+ "text": "My Bloggy Thing",
85
+ "logo": null,
86
+ "url": "/"
87
+ }
88
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.43.1",
3
+ "version": "0.44.0",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
package/scripts/build.js CHANGED
@@ -169,6 +169,60 @@ async function stampPluginVersions() {
169
169
  }
170
170
  }
171
171
 
172
+ /**
173
+ * Domma Tools is the one Domma asset npm cannot deliver.
174
+ *
175
+ * `domma-js` does not list `domma-tools.*` in its package `files`, so the pair under
176
+ * `admin/dist/domma/` can only ever arrive by hand-copying it out of a domma build. Meanwhile
177
+ * `/dist/domma/domma.min.js` is served straight from `node_modules/domma-js` (server.js), so a
178
+ * `npm install domma-js@next` silently upgrades one half of the admin and leaves the other on
179
+ * whatever version was last copied across. The tools bundle's own banner says
180
+ * `Requires: domma.min.js` — it is built against a specific core and is not version-independent.
181
+ *
182
+ * That skew is invisible: both files load, no import dangles, nothing throws. It surfaces later as
183
+ * a Theme Roller or Page Roller that misbehaves against a core it was not built for. This is the
184
+ * one place that can see both versions at once, so it is the place that checks.
185
+ *
186
+ * @returns {{ok: boolean, installed: string, found: Array<{rel: string, version: string|null}>, problems: string[]}}
187
+ */
188
+ function verifyDommaToolsVersion(toolsRelPaths) {
189
+ const installed = JSON.parse(
190
+ readFileSync(join(ROOT, 'node_modules/domma-js/package.json'), 'utf8'),
191
+ ).version;
192
+
193
+ // Banners differ between the two artefacts: "Domma Tools v0.38.0" (JS) and
194
+ // "Domma Tools CSS v0.38.0" (CSS). Match both rather than only the one you happened to open.
195
+ const BANNER = /\*\s*Domma Tools(?: CSS)? v(\d+\.\d+\.\d+)/;
196
+
197
+ const found = [];
198
+ const problems = [];
199
+
200
+ for (const rel of toolsRelPaths) {
201
+ const src = join(ROOT, rel);
202
+ if (!existsSync(src)) {
203
+ // admin/index.html loads both unconditionally, so an absent file is a dead <script>/<link>
204
+ // in the published admin. The copy step already tolerates absence; flag it rather than
205
+ // silently agreeing.
206
+ problems.push(`${rel} is missing — the published admin will reference a file that isn't there`);
207
+ continue;
208
+ }
209
+ // The banner is in the first few lines; no need to read a 230 KB bundle in full.
210
+ const head = readFileSync(src, 'utf8').slice(0, 512);
211
+ const match = head.match(BANNER);
212
+ if (!match) {
213
+ problems.push(`${rel} carries no recognisable Domma Tools banner — cannot confirm its version`);
214
+ found.push({rel, version: null});
215
+ continue;
216
+ }
217
+ found.push({rel, version: match[1]});
218
+ if (match[1] !== installed) {
219
+ problems.push(`${rel} is v${match[1]} but domma-js is v${installed}`);
220
+ }
221
+ }
222
+
223
+ return {ok: problems.length === 0, installed, found, problems};
224
+ }
225
+
172
226
  async function build() {
173
227
  // Clean and recreate _publish/
174
228
  if (existsSync(OUT)) rmSync(OUT, {recursive: true, force: true});
@@ -191,6 +245,32 @@ async function build() {
191
245
  delete pkg.scripts?.prepublishOnly;
192
246
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
193
247
 
248
+ // Scrub this machine's local config out of the staged copy. config/site.json
249
+ // is a working file on a development install, so it carries whatever SMTP
250
+ // host and site URL were last set here — published releases up to 0.43.1 put
251
+ // a LAN address on the public registry and handed every manual installer a
252
+ // mail host that does not exist for them. Same reasoning as the analytics
253
+ // counters in .npmignore: runtime state must not ride along in the tarball.
254
+ const sitePath = join(OUT, 'config', 'site.json');
255
+ if (existsSync(sitePath)) {
256
+ const site = JSON.parse(readFileSync(sitePath, 'utf8'));
257
+ site.url = 'http://localhost:4096';
258
+ if (site.smtp) {
259
+ site.smtp = {
260
+ host: '', port: 587, user: '', pass: '',
261
+ secure: false, fromAddress: '', fromName: '',
262
+ };
263
+ }
264
+ writeFileSync(sitePath, JSON.stringify(site, null, 4) + '\n');
265
+ }
266
+
267
+ // .bak files are migration leftovers from whichever machine built the
268
+ // release — never something an installer should receive.
269
+ for (const rel of ['config/navigation.json.bak', 'config/connections.json.bak']) {
270
+ const bak = join(OUT, rel);
271
+ if (existsSync(bak)) rmSync(bak, {force: true});
272
+ }
273
+
194
274
  // Copy plugin server-side code verbatim
195
275
  const pluginAsIs = await collectFiles('{' + PLUGIN_AS_IS_PATTERNS.join(',') + '}');
196
276
  for (const rel of pluginAsIs) {
@@ -212,6 +292,20 @@ async function build() {
212
292
  'admin/dist/domma/domma-tools.css',
213
293
  'admin/dist/domma/domma-tools.min.js',
214
294
  ];
295
+
296
+ // Version-skew gate — see verifyDommaToolsVersion() for why this cannot be left to npm.
297
+ const toolsCheck = verifyDommaToolsVersion(dommaTools);
298
+ if (toolsCheck.ok) {
299
+ console.log(`Domma Tools v${toolsCheck.installed} — matches installed domma-js`);
300
+ } else {
301
+ for (const problem of toolsCheck.problems) console.error(` ${problem}`);
302
+ throw new Error(
303
+ `Domma Tools is out of step with domma-js v${toolsCheck.installed} — copy `
304
+ + `domma-tools.min.js and domma-tools.css from that version's build into admin/dist/domma/, `
305
+ + `then rebuild`,
306
+ );
307
+ }
308
+
215
309
  for (const rel of dommaTools) {
216
310
  const src = join(ROOT, rel);
217
311
  if (existsSync(src)) {