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.
- package/admin/js/templates/effects.html +752 -752
- package/admin/js/templates/forms.html +17 -17
- package/admin/js/templates/my-profile.html +17 -17
- package/admin/js/templates/role-editor.html +70 -70
- package/admin/js/templates/roles.html +10 -10
- package/bin/lib/config-merge.js +44 -44
- package/config/plugins.json +1 -1
- package/config/site.json +86 -86
- package/package.json +1 -1
- package/scripts/build.js +94 -0
- package/server/middleware/auth.js +253 -253
- package/server/routes/api/auth.js +309 -309
- package/server/routes/api/navigation.js +42 -42
- package/server/services/email.js +167 -167
- package/server/services/userProfiles.js +199 -199
- package/server/services/users.js +302 -302
- package/config/navigation.json.bak +0 -31
|
@@ -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 & 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 & 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>
|
package/bin/lib/config-merge.js
CHANGED
|
@@ -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
|
+
}
|
package/config/plugins.json
CHANGED
package/config/site.json
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
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)) {
|