domma-cms 0.1.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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +469 -0
  3. package/admin/css/admin.css +1123 -0
  4. package/admin/index.html +72 -0
  5. package/admin/js/api.js +210 -0
  6. package/admin/js/app.js +270 -0
  7. package/admin/js/config/sidebar-config.js +107 -0
  8. package/admin/js/lib/card.js +63 -0
  9. package/admin/js/lib/image-editor.js +869 -0
  10. package/admin/js/lib/markdown-toolbar.js +421 -0
  11. package/admin/js/templates/dashboard.html +50 -0
  12. package/admin/js/templates/documentation.html +237 -0
  13. package/admin/js/templates/layouts.html +11 -0
  14. package/admin/js/templates/login.html +58 -0
  15. package/admin/js/templates/media.html +16 -0
  16. package/admin/js/templates/navigation.html +50 -0
  17. package/admin/js/templates/page-editor.html +126 -0
  18. package/admin/js/templates/pages.html +18 -0
  19. package/admin/js/templates/plugins.html +12 -0
  20. package/admin/js/templates/settings.html +190 -0
  21. package/admin/js/templates/tutorials.html +233 -0
  22. package/admin/js/templates/user-editor.html +12 -0
  23. package/admin/js/templates/users.html +10 -0
  24. package/admin/js/views/dashboard.js +48 -0
  25. package/admin/js/views/documentation.js +12 -0
  26. package/admin/js/views/index.js +33 -0
  27. package/admin/js/views/layouts.js +49 -0
  28. package/admin/js/views/login.js +254 -0
  29. package/admin/js/views/media.js +240 -0
  30. package/admin/js/views/navigation.js +152 -0
  31. package/admin/js/views/page-editor.js +479 -0
  32. package/admin/js/views/pages.js +64 -0
  33. package/admin/js/views/plugins.js +100 -0
  34. package/admin/js/views/settings.js +64 -0
  35. package/admin/js/views/tutorials.js +12 -0
  36. package/admin/js/views/user-editor.js +88 -0
  37. package/admin/js/views/users.js +73 -0
  38. package/bin/cli.js +334 -0
  39. package/config/auth.json +20 -0
  40. package/config/content.json +10 -0
  41. package/config/navigation.json +63 -0
  42. package/config/plugins.json +47 -0
  43. package/config/presets.json +34 -0
  44. package/config/server.json +6 -0
  45. package/config/site.json +33 -0
  46. package/package.json +67 -0
  47. package/plugins/back-to-top/admin/templates/back-to-top-settings.html +55 -0
  48. package/plugins/back-to-top/admin/views/back-to-top-settings.js +44 -0
  49. package/plugins/back-to-top/config.js +10 -0
  50. package/plugins/back-to-top/plugin.js +24 -0
  51. package/plugins/back-to-top/plugin.json +36 -0
  52. package/plugins/back-to-top/public/inject-body.html +105 -0
  53. package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +113 -0
  54. package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +73 -0
  55. package/plugins/cookie-consent/config.js +30 -0
  56. package/plugins/cookie-consent/plugin.js +24 -0
  57. package/plugins/cookie-consent/plugin.json +36 -0
  58. package/plugins/cookie-consent/public/inject-body.html +69 -0
  59. package/plugins/custom-css/admin/templates/custom-css.html +17 -0
  60. package/plugins/custom-css/admin/views/custom-css.js +35 -0
  61. package/plugins/custom-css/config.js +1 -0
  62. package/plugins/custom-css/data/custom.css +0 -0
  63. package/plugins/custom-css/plugin.js +63 -0
  64. package/plugins/custom-css/plugin.json +32 -0
  65. package/plugins/custom-css/public/inject-head.html +1 -0
  66. package/plugins/domma-effects/admin/templates/domma-effects.html +488 -0
  67. package/plugins/domma-effects/admin/views/domma-effects.js +56 -0
  68. package/plugins/domma-effects/config.js +9 -0
  69. package/plugins/domma-effects/plugin.js +22 -0
  70. package/plugins/domma-effects/plugin.json +36 -0
  71. package/plugins/domma-effects/public/celebrations/core/canvas.js +111 -0
  72. package/plugins/domma-effects/public/celebrations/core/particles.js +144 -0
  73. package/plugins/domma-effects/public/celebrations/core/physics.js +166 -0
  74. package/plugins/domma-effects/public/celebrations/index.js +535 -0
  75. package/plugins/domma-effects/public/celebrations/themes/christmas.js +1805 -0
  76. package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1477 -0
  77. package/plugins/domma-effects/public/celebrations/themes/halloween.js +1837 -0
  78. package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1175 -0
  79. package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1258 -0
  80. package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1754 -0
  81. package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1290 -0
  82. package/plugins/domma-effects/public/celebrations/themes/valentines.js +1361 -0
  83. package/plugins/domma-effects/public/inject-body.html +268 -0
  84. package/plugins/example-analytics/admin/templates/analytics.html +10 -0
  85. package/plugins/example-analytics/admin/views/analytics.js +51 -0
  86. package/plugins/example-analytics/config.js +6 -0
  87. package/plugins/example-analytics/plugin.js +58 -0
  88. package/plugins/example-analytics/plugin.json +27 -0
  89. package/plugins/example-analytics/public/inject-body.html +13 -0
  90. package/plugins/example-analytics/public/inject-head.html +1 -0
  91. package/plugins/example-analytics/stats.json +1 -0
  92. package/plugins/form-builder/admin/templates/form-editor.html +158 -0
  93. package/plugins/form-builder/admin/templates/form-settings.html +29 -0
  94. package/plugins/form-builder/admin/templates/form-submissions.html +30 -0
  95. package/plugins/form-builder/admin/templates/forms-list.html +17 -0
  96. package/plugins/form-builder/admin/views/form-editor.js +817 -0
  97. package/plugins/form-builder/admin/views/form-settings.js +38 -0
  98. package/plugins/form-builder/admin/views/form-submissions.js +295 -0
  99. package/plugins/form-builder/admin/views/forms-list.js +164 -0
  100. package/plugins/form-builder/config.js +9 -0
  101. package/plugins/form-builder/data/forms/contact-details.json +63 -0
  102. package/plugins/form-builder/data/forms/contact.json +52 -0
  103. package/plugins/form-builder/data/submissions/contact-details.json +1 -0
  104. package/plugins/form-builder/data/submissions/contact.json +14 -0
  105. package/plugins/form-builder/email.js +103 -0
  106. package/plugins/form-builder/plugin.js +454 -0
  107. package/plugins/form-builder/plugin.json +56 -0
  108. package/plugins/form-builder/public/inject-body.html +270 -0
  109. package/plugins/form-builder/public/inject-head.html +42 -0
  110. package/public/css/site.css +189 -0
  111. package/public/js/site.js +109 -0
  112. package/scripts/copy-domma.js +48 -0
  113. package/scripts/fresh.js +41 -0
  114. package/scripts/reset.js +124 -0
  115. package/scripts/seed.js +666 -0
  116. package/scripts/setup.js +263 -0
  117. package/server/config.js +56 -0
  118. package/server/middleware/auth.js +97 -0
  119. package/server/routes/api/auth.js +116 -0
  120. package/server/routes/api/layouts.js +25 -0
  121. package/server/routes/api/media.js +93 -0
  122. package/server/routes/api/navigation.js +37 -0
  123. package/server/routes/api/pages.js +118 -0
  124. package/server/routes/api/plugins.js +46 -0
  125. package/server/routes/api/settings.js +25 -0
  126. package/server/routes/api/users.js +110 -0
  127. package/server/routes/public.js +108 -0
  128. package/server/server.js +169 -0
  129. package/server/services/content.js +298 -0
  130. package/server/services/images.js +334 -0
  131. package/server/services/markdown.js +297 -0
  132. package/server/services/plugins.js +246 -0
  133. package/server/services/renderer.js +80 -0
  134. package/server/services/users.js +212 -0
  135. package/server/templates/page.html +78 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Settings View
3
+ */
4
+ import { api } from '../api.js';
5
+
6
+ export const settingsView = {
7
+ templateUrl: '/admin/js/templates/settings.html',
8
+
9
+ async onMount($container) {
10
+ const s = await api.settings.get().catch(() => ({}));
11
+ $container.find('#field-site-title').val(s.title || '');
12
+ $container.find('#field-tagline').val(s.tagline || '');
13
+ $container.find('#field-theme').val(s.theme || 'charcoal-dark');
14
+ $container.find('#field-admin-theme').val(s.adminTheme || 'charcoal-dark');
15
+ $container.find('#field-seo-title').val(s.seo?.defaultTitle || '');
16
+ $container.find('#field-seo-separator').val(s.seo?.titleSeparator || ' | ');
17
+ $container.find('#field-seo-desc').val(s.seo?.defaultDescription || '');
18
+ $container.find('#field-footer-copy').val(s.footer?.copyright || '');
19
+
20
+ // SMTP fields
21
+ $container.find('#field-smtp-host').val(s.smtp?.host || '');
22
+ $container.find('#field-smtp-port').val(s.smtp?.port || 587);
23
+ $container.find('#field-smtp-user').val(s.smtp?.user || '');
24
+ $container.find('#field-smtp-pass').val(s.smtp?.pass || '');
25
+ $container.find('#field-smtp-secure').prop('checked', s.smtp?.secure || false);
26
+ $container.find('#field-smtp-from-address').val(s.smtp?.fromAddress || '');
27
+ $container.find('#field-smtp-from-name').val(s.smtp?.fromName || '');
28
+
29
+ $container.find('#save-settings-btn').on('click', async () => {
30
+ const adminTheme = $container.find('#field-admin-theme').val();
31
+ const data = {
32
+ title: $container.find('#field-site-title').val().trim(),
33
+ tagline: $container.find('#field-tagline').val().trim(),
34
+ theme: $container.find('#field-theme').val(),
35
+ adminTheme,
36
+ seo: {
37
+ defaultTitle: $container.find('#field-seo-title').val().trim(),
38
+ titleSeparator: $container.find('#field-seo-separator').val() || ' | ',
39
+ defaultDescription: $container.find('#field-seo-desc').val().trim()
40
+ },
41
+ footer: {
42
+ copyright: $container.find('#field-footer-copy').val().trim(),
43
+ links: s.footer?.links || []
44
+ },
45
+ smtp: {
46
+ host: $container.find('#field-smtp-host').val().trim(),
47
+ port: parseInt($container.find('#field-smtp-port').val(), 10) || 587,
48
+ user: $container.find('#field-smtp-user').val().trim(),
49
+ pass: $container.find('#field-smtp-pass').val(),
50
+ secure: $container.find('#field-smtp-secure').prop('checked'),
51
+ fromAddress: $container.find('#field-smtp-from-address').val().trim(),
52
+ fromName: $container.find('#field-smtp-from-name').val().trim()
53
+ }
54
+ };
55
+ try {
56
+ await api.settings.save(data);
57
+ Domma.theme.set(adminTheme);
58
+ E.toast('Settings saved.', { type: 'success' });
59
+ } catch {
60
+ E.toast('Failed to save settings.', { type: 'error' });
61
+ }
62
+ });
63
+ }
64
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Tutorials View
3
+ * Step-by-step guides for extending and customising Domma CMS.
4
+ */
5
+ export const tutorialsView = {
6
+ templateUrl: '/admin/js/templates/tutorials.html',
7
+
8
+ async onMount($container) {
9
+ Domma.icons.scan();
10
+ Domma.syntax.scan();
11
+ }
12
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * User Editor View
3
+ * Handles both create (new) and edit (existing) modes using F.render().
4
+ */
5
+ import { api } from '../api.js';
6
+
7
+ const ROLES = [
8
+ { value: 'admin', label: 'Admin' },
9
+ { value: 'manager', label: 'Manager' },
10
+ { value: 'editor', label: 'Editor' },
11
+ { value: 'subscriber', label: 'Subscriber' }
12
+ ];
13
+
14
+ export const userEditorView = {
15
+ templateUrl: '/admin/js/templates/user-editor.html',
16
+
17
+ async onMount($container) {
18
+ const hash = window.location.hash;
19
+ const editMatch = hash.match(/#\/users\/edit\/([^/]+)/);
20
+ const userId = editMatch ? editMatch[1] : null;
21
+ const isEdit = !!userId;
22
+
23
+ let user = null;
24
+ if (isEdit) {
25
+ user = await api.users.get(userId).catch(() => null);
26
+ if (!user) {
27
+ E.toast('User not found.', { type: 'error' });
28
+ R.navigate('/users');
29
+ return;
30
+ }
31
+ }
32
+
33
+ $container.find('#editor-title').text(isEdit ? 'Edit User' : 'New User');
34
+ $container.find('#cancel-btn').on('click', () => R.navigate('/users'));
35
+
36
+ const blueprint = {
37
+ name: { type: 'string', required: true, minLength: 2, label: 'Full Name',
38
+ formConfig: { placeholder: 'Jane Smith' } },
39
+ email: { type: 'email', required: true, label: 'Email Address',
40
+ formConfig: { placeholder: 'jane@example.com' } },
41
+ role: { type: 'select', required: true, label: 'Role',
42
+ options: ROLES },
43
+ isActive: { type: 'boolean', label: 'Active account' },
44
+ password: { type: 'password', required: !isEdit, minLength: isEdit ? 0 : 8,
45
+ label: isEdit ? 'New Password' : 'Password',
46
+ formConfig: { placeholder: '••••••••', autocomplete: 'new-password',
47
+ tooltip: isEdit ? 'Leave blank to keep current password' : 'Minimum 8 characters' } }
48
+ };
49
+
50
+ const initialData = user
51
+ ? { name: user.name || '', email: user.email || '', role: user.role || 'editor', isActive: user.isActive !== false }
52
+ : { role: 'editor', isActive: true };
53
+
54
+ F.render('#user-form-container', blueprint, initialData, {
55
+ layout: 'stacked',
56
+ submitText: isEdit ? 'Update User' : 'Create User',
57
+ onSubmit: async (data) => {
58
+ if (isEdit && data.password && data.password.length < 8) {
59
+ E.toast('Password must be at least 8 characters.', { type: 'warning' });
60
+ return false;
61
+ }
62
+
63
+ const payload = {
64
+ name: data.name,
65
+ email: data.email,
66
+ role: data.role,
67
+ isActive: !!data.isActive
68
+ };
69
+ if (data.password) payload.password = data.password;
70
+
71
+ try {
72
+ if (isEdit) {
73
+ await api.users.update(userId, payload);
74
+ } else {
75
+ await api.users.create(payload);
76
+ }
77
+ E.toast('User saved successfully.', { type: 'success' });
78
+ R.navigate('/users');
79
+ } catch (err) {
80
+ E.toast(`Save failed: ${err.message || 'Unknown error'}`, { type: 'error' });
81
+ return false;
82
+ }
83
+ }
84
+ });
85
+
86
+ Domma.icons.scan();
87
+ }
88
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Users List View
3
+ * Shows all users in a Domma table with role badges.
4
+ */
5
+ import { api, getUser } from '../api.js';
6
+
7
+ const ROLE_BADGE = {
8
+ admin: 'badge-danger',
9
+ manager: 'badge-warning',
10
+ editor: 'badge-info',
11
+ subscriber: 'badge-secondary'
12
+ };
13
+
14
+ export const usersView = {
15
+ templateUrl: '/admin/js/templates/users.html',
16
+
17
+ async onMount($container) {
18
+ const currentUser = getUser();
19
+ let users = await api.users.list().catch(() => []);
20
+
21
+ const renderTable = (data) => {
22
+ T.create('#users-table', {
23
+ data,
24
+ columns: [
25
+ { key: 'name', label: 'Name' },
26
+ { key: 'email', label: 'Email' },
27
+ { key: 'role', label: 'Role',
28
+ render: (val) => `<span class="badge ${ROLE_BADGE[val] || 'badge-secondary'}">${val}</span>` },
29
+ { key: 'isActive', label: 'Status',
30
+ render: (val) => val
31
+ ? '<span class="badge badge-success">Active</span>'
32
+ : '<span class="badge badge-secondary">Inactive</span>' },
33
+ { key: 'lastLogin', label: 'Last login',
34
+ render: (val) => val ? D(val).format('DD MMM YYYY HH:mm') : 'Never' },
35
+ {
36
+ key: 'actions', label: 'Actions',
37
+ render: (_, row) => {
38
+ const isSelf = row.id === currentUser?.id;
39
+ return `
40
+ <a href="#/users/edit/${row.id}" class="btn btn-sm btn-outline">Edit</a>
41
+ ${!isSelf ? `<button class="btn btn-sm btn-danger btn-delete-user" data-id="${row.id}" data-name="${escapeAttr(row.name)}">Delete</button>` : ''}
42
+ `;
43
+ }
44
+ }
45
+ ],
46
+ emptyMessage: 'No users found.'
47
+ });
48
+ Domma.icons.scan();
49
+ Domma.effects.reveal('.card', { animation: 'fade', duration: 350 });
50
+ };
51
+
52
+ renderTable(users);
53
+
54
+ $container.off('click', '.btn-delete-user').on('click', '.btn-delete-user', async function () {
55
+ const id = $(this).data('id');
56
+ const name = $(this).data('name');
57
+ const confirmed = await E.confirm(`Delete user <strong>${name}</strong>? This cannot be undone.`);
58
+ if (!confirmed) return;
59
+ try {
60
+ await api.users.delete(id);
61
+ E.toast('User deleted.', { type: 'success' });
62
+ users = users.filter(u => u.id !== id);
63
+ renderTable(users);
64
+ } catch (err) {
65
+ E.toast(`Failed to delete: ${err.message}`, { type: 'error' });
66
+ }
67
+ });
68
+ }
69
+ };
70
+
71
+ function escapeAttr(str) {
72
+ return String(str).replace(/"/g, '&quot;');
73
+ }
package/bin/cli.js ADDED
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Domma CMS — Project Scaffolder
4
+ * Usage: npx domma-cms <project-name> [--no-install] [--no-setup] [--seed]
5
+ */
6
+
7
+ import {cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync} from 'node:fs';
8
+ import {spawnSync} from 'node:child_process';
9
+ import path from 'node:path';
10
+ import {fileURLToPath} from 'node:url';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Paths
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Node version check
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const [major] = process.versions.node.split('.').map(Number);
25
+ if (major < 18) {
26
+ console.error('\n ✗ Domma CMS requires Node.js 18 or higher.\n');
27
+ process.exit(1);
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Arg parsing
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const args = process.argv.slice(2);
35
+ const flags = new Set(args.filter(a => a.startsWith('--')));
36
+ const positional = args.filter(a => !a.startsWith('--'));
37
+
38
+ if (flags.has('--help') || args.includes('-h')) {
39
+ console.log(`
40
+ Usage: npx domma-cms <project-name> [options]
41
+
42
+ Options:
43
+ --no-install Skip npm install
44
+ --no-setup Skip the interactive setup wizard
45
+ --seed Run npm run seed after setup
46
+ --help Show this help message
47
+
48
+ Example:
49
+ npx domma-cms my-blog
50
+ npx domma-cms my-blog --no-install --no-setup
51
+ `);
52
+ process.exit(0);
53
+ }
54
+
55
+ const projectName = positional[0];
56
+
57
+ if (!projectName) {
58
+ console.error('\n ✗ Project name is required.\n');
59
+ console.error(' Usage: npx domma-cms <project-name>\n');
60
+ process.exit(1);
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Validation
65
+ // ---------------------------------------------------------------------------
66
+
67
+ if (!/^[a-z0-9._-]+$/i.test(projectName)) {
68
+ console.error(`\n ✗ Invalid project name "${projectName}".`);
69
+ console.error(' Use only lowercase letters, numbers, hyphens, dots, or underscores.\n');
70
+ process.exit(1);
71
+ }
72
+
73
+ const target = path.resolve(process.cwd(), projectName);
74
+
75
+ if (existsSync(target)) {
76
+ const contents = readdirSync(target);
77
+ if (contents.length > 0) {
78
+ console.error(`\n ✗ Directory "${projectName}" already exists and is not empty.\n`);
79
+ process.exit(1);
80
+ }
81
+ }
82
+
83
+ const noInstall = flags.has('--no-install');
84
+ const noSetup = flags.has('--no-setup') || noInstall;
85
+ const withSeed = flags.has('--seed');
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Banner
89
+ // ---------------------------------------------------------------------------
90
+
91
+ console.log('');
92
+ console.log(' ┌──────────────────────────────────────────┐');
93
+ console.log(' │ │');
94
+ console.log(' │ Domma CMS — Project Scaffolder │');
95
+ console.log(' │ │');
96
+ console.log(' └──────────────────────────────────────────┘');
97
+ console.log('');
98
+ console.log(` Creating project: ${projectName}`);
99
+ console.log(` Target: ${target}`);
100
+ console.log('');
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Helpers
104
+ // ---------------------------------------------------------------------------
105
+
106
+ function step(label) {
107
+ process.stdout.write(` ${label}…`);
108
+ }
109
+
110
+ function done() {
111
+ console.log(' done.');
112
+ }
113
+
114
+ function copyDir(src, dest) {
115
+ cpSync(path.join(PACKAGE_ROOT, src), path.join(target, dest ?? src), {recursive: true});
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Create project structure
120
+ // ---------------------------------------------------------------------------
121
+
122
+ step('Creating directory');
123
+ mkdirSync(target, {recursive: true});
124
+ done();
125
+
126
+ step('Copying server');
127
+ copyDir('server');
128
+ done();
129
+
130
+ step('Copying admin');
131
+ copyDir('admin');
132
+ done();
133
+
134
+ step('Copying public');
135
+ copyDir('public');
136
+ done();
137
+
138
+ step('Copying scripts');
139
+ copyDir('scripts');
140
+ done();
141
+
142
+ step('Copying config');
143
+ copyDir('config');
144
+ done();
145
+
146
+ step('Copying plugins');
147
+ copyDir('plugins');
148
+ done();
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Reset data files inside copied plugins
152
+ // ---------------------------------------------------------------------------
153
+
154
+ step('Resetting plugin data');
155
+ writeFileSync(path.join(target, 'plugins/example-analytics/stats.json'), '{}\n', 'utf8');
156
+ writeFileSync(path.join(target, 'plugins/form-builder/data/submissions/contact.json'), '[]\n', 'utf8');
157
+ done();
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Reset config to clean defaults
161
+ // ---------------------------------------------------------------------------
162
+
163
+ const DEFAULT_SITE = {
164
+ title: 'My Site',
165
+ tagline: 'Powered by Domma CMS',
166
+ theme: 'charcoal-dark',
167
+ seo: {
168
+ defaultTitle: 'My Site',
169
+ titleSeparator: ' | ',
170
+ defaultDescription: 'A site built with Domma CMS'
171
+ },
172
+ footer: {
173
+ copyright: `© ${new Date().getFullYear()} My Site. All rights reserved.`,
174
+ links: [
175
+ {text: 'Privacy Policy', url: '/privacy'},
176
+ {text: 'Contact', url: '/contact'}
177
+ ]
178
+ }
179
+ };
180
+
181
+ const DEFAULT_NAV = {
182
+ brand: {text: 'My Site', logo: null, url: '/'},
183
+ items: [
184
+ {text: 'Home', url: '/', icon: 'home'},
185
+ {text: 'About', url: '/about', icon: 'info'},
186
+ {text: 'Contact', url: '/contact', icon: 'mail'}
187
+ ],
188
+ variant: 'dark',
189
+ position: 'sticky'
190
+ };
191
+
192
+ // All plugins present but disabled by default in a fresh project
193
+ const DEFAULT_PLUGINS = {
194
+ 'example-analytics': {enabled: false, settings: {}},
195
+ 'contact-form': {enabled: false, settings: {}},
196
+ 'form-builder': {enabled: false, settings: {}},
197
+ 'back-to-top': {enabled: false, settings: {}},
198
+ 'cookie-consent': {enabled: false, settings: {}}
199
+ };
200
+
201
+ step('Writing default config');
202
+ writeFileSync(path.join(target, 'config/site.json'), JSON.stringify(DEFAULT_SITE, null, 4) + '\n', 'utf8');
203
+ writeFileSync(path.join(target, 'config/navigation.json'), JSON.stringify(DEFAULT_NAV, null, 4) + '\n', 'utf8');
204
+ writeFileSync(path.join(target, 'config/plugins.json'), JSON.stringify(DEFAULT_PLUGINS, null, 4) + '\n', 'utf8');
205
+ done();
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Create empty content directories
209
+ // ---------------------------------------------------------------------------
210
+
211
+ step('Creating content directories');
212
+ mkdirSync(path.join(target, 'content/pages'), {recursive: true});
213
+ mkdirSync(path.join(target, 'content/media'), {recursive: true});
214
+ mkdirSync(path.join(target, 'content/users'), {recursive: true});
215
+ done();
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // Write project package.json (runtime deps only, private: true)
219
+ // ---------------------------------------------------------------------------
220
+
221
+ step('Writing package.json');
222
+ const sourcePkg = JSON.parse(readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
223
+ const projectPkg = {
224
+ name: projectName,
225
+ version: '0.1.0',
226
+ description: `${projectName} — powered by Domma CMS`,
227
+ type: 'module',
228
+ private: true,
229
+ main: 'server/server.js',
230
+ scripts: sourcePkg.scripts,
231
+ dependencies: sourcePkg.dependencies
232
+ };
233
+ writeFileSync(path.join(target, 'package.json'), JSON.stringify(projectPkg, null, 2) + '\n', 'utf8');
234
+ done();
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Write .env.example and .env
238
+ // ---------------------------------------------------------------------------
239
+
240
+ const ENV_EXAMPLE = `# Domma CMS — Environment Variables
241
+ # Auto-generated by the setup wizard (npm run setup)
242
+
243
+ JWT_SECRET=CHANGE_ME
244
+ NODE_ENV=development
245
+ `;
246
+
247
+ step('Writing .env files');
248
+ writeFileSync(path.join(target, '.env.example'), ENV_EXAMPLE, 'utf8');
249
+ writeFileSync(path.join(target, '.env'), ENV_EXAMPLE, 'utf8');
250
+ done();
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // Write .gitignore
254
+ // ---------------------------------------------------------------------------
255
+
256
+ const GITIGNORE = `node_modules/
257
+ .env
258
+ content/users/
259
+ content/media/
260
+ *.log
261
+ `;
262
+
263
+ step('Writing .gitignore');
264
+ writeFileSync(path.join(target, '.gitignore'), GITIGNORE, 'utf8');
265
+ done();
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // npm install
269
+ // ---------------------------------------------------------------------------
270
+
271
+ console.log('');
272
+
273
+ if (noInstall) {
274
+ console.log(' ⚠ Skipping npm install (--no-install).');
275
+ } else {
276
+ console.log(' Running npm install…');
277
+ console.log('');
278
+ const result = spawnSync('npm', ['install'], {cwd: target, stdio: 'inherit'});
279
+ if (result.status !== 0) {
280
+ console.error('\n ✗ npm install failed.\n');
281
+ process.exit(result.status ?? 1);
282
+ }
283
+ console.log('');
284
+ }
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // Setup wizard
288
+ // ---------------------------------------------------------------------------
289
+
290
+ if (noSetup) {
291
+ if (!noInstall) console.log(' ⚠ Skipping setup wizard (--no-setup).');
292
+ } else {
293
+ console.log(' Running setup wizard (npm run setup)…');
294
+ console.log('');
295
+ const result = spawnSync(process.execPath, ['scripts/setup.js'], {cwd: target, stdio: 'inherit'});
296
+ if (result.status !== 0) {
297
+ console.error('\n ✗ Setup failed.\n');
298
+ process.exit(result.status ?? 1);
299
+ }
300
+ console.log('');
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Optional seed
305
+ // ---------------------------------------------------------------------------
306
+
307
+ if (withSeed) {
308
+ console.log(' Running seed (npm run seed)…');
309
+ console.log('');
310
+ const result = spawnSync('npm', ['run', 'seed'], {cwd: target, stdio: 'inherit'});
311
+ if (result.status !== 0) {
312
+ console.error('\n ✗ Seed failed.\n');
313
+ process.exit(result.status ?? 1);
314
+ }
315
+ console.log('');
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Success
320
+ // ---------------------------------------------------------------------------
321
+
322
+ console.log('');
323
+ console.log(' ┌──────────────────────────────────────────┐');
324
+ console.log(' │ ✓ Project created successfully! │');
325
+ console.log(' └──────────────────────────────────────────┘');
326
+ console.log('');
327
+ console.log(` Next steps:`);
328
+ console.log(` cd ${projectName}`);
329
+ if (noInstall) console.log(` npm install`);
330
+ if (noSetup) console.log(` npm run setup`);
331
+ console.log(` npm run dev`);
332
+ console.log('');
333
+ console.log(` Then open: http://localhost:3050/admin`);
334
+ console.log('');
@@ -0,0 +1,20 @@
1
+ {
2
+ "accessTokenExpiry": "15m",
3
+ "refreshTokenExpiry": "7d",
4
+ "bcryptRounds": 10,
5
+ "roles": {
6
+ "admin": { "label": "Admin", "level": 0 },
7
+ "manager": { "label": "Manager", "level": 1 },
8
+ "editor": { "label": "Editor", "level": 2 },
9
+ "subscriber": { "label": "Subscriber", "level": 3 }
10
+ },
11
+ "permissions": {
12
+ "pages": ["admin", "manager", "editor"],
13
+ "settings": ["admin", "manager"],
14
+ "navigation": ["admin", "manager"],
15
+ "layouts": ["admin", "manager"],
16
+ "media": ["admin", "manager", "editor"],
17
+ "users": ["admin", "manager"],
18
+ "plugins": ["admin"]
19
+ }
20
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "contentDir": "./content",
3
+ "mediaDir": "./content/media",
4
+ "usersDir": "./content/users",
5
+ "pageDefaults": {
6
+ "layout": "default",
7
+ "status": "draft",
8
+ "sortOrder": 99
9
+ }
10
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "brand": {
3
+ "text": "My Site",
4
+ "logo": null,
5
+ "url": "/"
6
+ },
7
+ "items": [
8
+ {
9
+ "text": "Home",
10
+ "url": "/",
11
+ "icon": "home"
12
+ },
13
+ {
14
+ "text": "About",
15
+ "url": "/about",
16
+ "icon": "info"
17
+ },
18
+ {
19
+ "text": "Blog",
20
+ "url": "/blog",
21
+ "icon": "book-open"
22
+ },
23
+ {
24
+ "text": "Contact",
25
+ "url": "/contact",
26
+ "icon": "mail"
27
+ },
28
+ {
29
+ "text": "Resources",
30
+ "url": "/resources",
31
+ "icon": "book-open",
32
+ "items": [
33
+ {
34
+ "text": "Overview",
35
+ "url": "/resources",
36
+ "icon": "grid"
37
+ },
38
+ {
39
+ "text": "Typography",
40
+ "url": "/resources/typography",
41
+ "icon": "type"
42
+ },
43
+ {
44
+ "text": "Grid System",
45
+ "url": "/resources/grid",
46
+ "icon": "layout"
47
+ },
48
+ {
49
+ "text": "Cards",
50
+ "url": "/resources/cards",
51
+ "icon": "credit-card"
52
+ },
53
+ {
54
+ "text": "Shortcode Reference",
55
+ "url": "/resources/shortcodes",
56
+ "icon": "code"
57
+ }
58
+ ]
59
+ }
60
+ ],
61
+ "variant": "dark",
62
+ "position": "sticky"
63
+ }