domma-cms 0.5.4 → 0.6.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/bin/cli.js CHANGED
@@ -37,21 +37,44 @@ const positional = args.filter(a => !a.startsWith('--'));
37
37
 
38
38
  if (flags.has('--help') || args.includes('-h')) {
39
39
  console.log(`
40
- Usage: npx domma-cms <project-name> [options]
40
+ Usage: npx domma-cms <command> [options]
41
41
 
42
- Options:
42
+ Commands:
43
+ <project-name> Scaffold a new Domma CMS project
44
+ update Update an existing project to the latest version
45
+
46
+ Scaffold options:
43
47
  --no-install Skip npm install
44
48
  --no-setup Skip the interactive setup wizard
45
49
  --no-seed Skip seeding default pages, forms, and collections
46
50
  --help Show this help message
47
51
 
48
- Example:
52
+ Update options:
53
+ --yes Skip confirmation prompt
54
+ --no-backup Skip creating a backup before updating
55
+ --no-install Skip npm install after updating
56
+ --dry-run Preview what would change without writing anything
57
+
58
+ Examples:
49
59
  npx domma-cms my-blog
50
60
  npx domma-cms my-blog --no-install --no-setup
61
+ npx domma-cms update
62
+ npx domma-cms update --dry-run
63
+ npx domma-cms update --yes --no-backup
51
64
  `);
52
65
  process.exit(0);
53
66
  }
54
67
 
68
+ // ---------------------------------------------------------------------------
69
+ // Route subcommands
70
+ // ---------------------------------------------------------------------------
71
+
72
+ if (positional[0] === 'update') {
73
+ const {default: runUpdate} = await import('./update.js');
74
+ await runUpdate(positional.slice(1), flags);
75
+ process.exit(0);
76
+ }
77
+
55
78
  const projectName = positional[0];
56
79
 
57
80
  if (!projectName) {
@@ -292,6 +315,7 @@ const GITIGNORE = `node_modules/
292
315
  content/users/
293
316
  content/media/
294
317
  *.log
318
+ .domma-backups/
295
319
  `;
296
320
 
297
321
  step('Writing .gitignore');
@@ -351,6 +375,19 @@ if (noSeed) {
351
375
  console.log('');
352
376
  }
353
377
 
378
+ // ---------------------------------------------------------------------------
379
+ // Write .domma version marker
380
+ // ---------------------------------------------------------------------------
381
+
382
+ step('Writing .domma marker');
383
+ const marker = {
384
+ cmsVersion: sourcePkg.version,
385
+ scaffoldedAt: new Date().toISOString(),
386
+ lastUpdatedAt: null
387
+ };
388
+ writeFileSync(path.join(target, '.domma'), JSON.stringify(marker, null, 2) + '\n', 'utf8');
389
+ done();
390
+
354
391
  // ---------------------------------------------------------------------------
355
392
  // Success
356
393
  // ---------------------------------------------------------------------------
@@ -0,0 +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
+ }
package/bin/update.js ADDED
@@ -0,0 +1,542 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Domma CMS — Project Update
4
+ * Usage: npx domma-cms update [--yes] [--no-backup] [--no-install] [--dry-run]
5
+ *
6
+ * Run from inside an existing Domma CMS project directory.
7
+ * Pulls upstream changes from the installed CLI package into the project,
8
+ * preserving all user content, config, and custom plugins.
9
+ */
10
+
11
+ import {cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync,} from 'node:fs';
12
+ import {spawnSync} from 'node:child_process';
13
+ import path from 'node:path';
14
+ import {createInterface} from 'node:readline';
15
+ import {fileURLToPath} from 'node:url';
16
+ import {deepMergeNewKeys} from './lib/config-merge.js';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Paths
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Helpers
28
+ // ---------------------------------------------------------------------------
29
+
30
+ function step(label) {
31
+ process.stdout.write(` ${label}…`);
32
+ }
33
+
34
+ function done(note = '') {
35
+ console.log(note ? ` done. ${note}` : ' done.');
36
+ }
37
+
38
+ function info(msg) {
39
+ console.log(` ${msg}`);
40
+ }
41
+
42
+ function warn(msg) {
43
+ console.log(` ⚠ ${msg}`);
44
+ }
45
+
46
+ function readJson(filePath) {
47
+ return JSON.parse(readFileSync(filePath, 'utf8'));
48
+ }
49
+
50
+ function writeJson(filePath, obj) {
51
+ writeFileSync(filePath, JSON.stringify(obj, null, 4) + '\n', 'utf8');
52
+ }
53
+
54
+ /**
55
+ * Prompt the user for a yes/no confirmation.
56
+ * @param {string} question
57
+ * @returns {Promise<boolean>}
58
+ */
59
+ function confirm(question) {
60
+ return new Promise(resolve => {
61
+ const rl = createInterface({input: process.stdin, output: process.stdout});
62
+ rl.question(` ${question} [y/N] `, answer => {
63
+ rl.close();
64
+ resolve(/^y(es)?$/i.test(answer.trim()));
65
+ });
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Recursively list all relative file paths under a directory.
71
+ * @param {string} dir - Absolute directory path
72
+ * @param {string} [base] - Base path for relative output (defaults to dir)
73
+ * @returns {string[]}
74
+ */
75
+ function listFiles(dir, base = dir) {
76
+ if (!existsSync(dir)) return [];
77
+ const result = [];
78
+ for (const entry of readdirSync(dir, {withFileTypes: true})) {
79
+ const full = path.join(dir, entry.name);
80
+ if (entry.isDirectory()) {
81
+ result.push(...listFiles(full, base));
82
+ } else {
83
+ result.push(path.relative(base, full));
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Upstream dirs that are fully replaced on update
91
+ // ---------------------------------------------------------------------------
92
+
93
+ const UPSTREAM_DIRS = ['server', 'admin', 'public', 'scripts'];
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Structural config files that get new-key merging (never full replace)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ const STRUCTURAL_CONFIGS = [
100
+ 'config/server.json',
101
+ 'config/auth.json',
102
+ 'config/content.json',
103
+ 'config/presets.json',
104
+ ];
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // Main update function
108
+ // ---------------------------------------------------------------------------
109
+
110
+ /**
111
+ * @param {string[]} _positional - Remaining positional args (unused)
112
+ * @param {Set<string>} flags - Parsed CLI flags
113
+ */
114
+ export default async function update(_positional, flags) {
115
+ const isDryRun = flags.has('--dry-run');
116
+ const skipConfirm = flags.has('--yes');
117
+ const skipBackup = flags.has('--no-backup');
118
+ const skipInstall = flags.has('--no-install');
119
+
120
+ const cwd = process.cwd();
121
+
122
+ // -------------------------------------------------------------------------
123
+ // 1. Detect project
124
+ // -------------------------------------------------------------------------
125
+
126
+ console.log('');
127
+ console.log(' ┌──────────────────────────────────────────┐');
128
+ console.log(' │ │');
129
+ console.log(' │ Domma CMS — Project Updater │');
130
+ console.log(' │ │');
131
+ console.log(' └──────────────────────────────────────────┘');
132
+ console.log('');
133
+
134
+ // Refuse to run inside the CLI package itself
135
+ if (path.resolve(cwd) === path.resolve(PACKAGE_ROOT)) {
136
+ console.error(' ✗ Run this command from inside your scaffolded project, not the CLI package.\n');
137
+ process.exit(1);
138
+ }
139
+
140
+ const requiredFiles = ['package.json', 'server/server.js', 'config'];
141
+ for (const f of requiredFiles) {
142
+ if (!existsSync(path.join(cwd, f))) {
143
+ console.error(` ✗ Not a Domma CMS project directory (missing ${f}).\n`);
144
+ console.error(' Run this command from inside your project folder.\n');
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ // Scaffolded projects are always private — reject if not
150
+ try {
151
+ const cwdPkg = readJson(path.join(cwd, 'package.json'));
152
+ if (!cwdPkg.private) {
153
+ console.error(' ✗ This does not appear to be a scaffolded Domma CMS project (package.json is not private).\n');
154
+ process.exit(1);
155
+ }
156
+ } catch { /* malformed package.json — let it proceed */
157
+ }
158
+
159
+ // -------------------------------------------------------------------------
160
+ // 2. Read version information
161
+ // -------------------------------------------------------------------------
162
+
163
+ const markerPath = path.join(cwd, '.domma');
164
+ let installedVersion = 'unknown';
165
+ let markerData = null;
166
+
167
+ if (existsSync(markerPath)) {
168
+ try {
169
+ markerData = readJson(markerPath);
170
+ installedVersion = markerData.cmsVersion ?? 'unknown';
171
+ } catch {
172
+ warn('Could not read .domma marker — treating as unknown version.');
173
+ }
174
+ } else {
175
+ warn('No .domma marker found. This project was scaffolded before version tracking was introduced.');
176
+ warn('The updater will proceed, but cannot guarantee what was previously installed.');
177
+ console.log('');
178
+ }
179
+
180
+ const sourcePkg = readJson(path.join(PACKAGE_ROOT, 'package.json'));
181
+ const incomingVersion = sourcePkg.version;
182
+
183
+ info(`Current: ${installedVersion}`);
184
+ info(`Available: ${incomingVersion}`);
185
+ console.log('');
186
+
187
+ if (installedVersion !== 'unknown' && installedVersion === incomingVersion) {
188
+ info('✓ Already up to date.');
189
+ console.log('');
190
+ process.exit(0);
191
+ }
192
+
193
+ // -------------------------------------------------------------------------
194
+ // 3. Dry-run mode — preview changes and exit
195
+ // -------------------------------------------------------------------------
196
+
197
+ if (isDryRun) {
198
+ info('Dry run — no files will be modified.\n');
199
+
200
+ info('Upstream directories that would be replaced:');
201
+ for (const dir of UPSTREAM_DIRS) {
202
+ info(` → ${dir}/`);
203
+ }
204
+ console.log('');
205
+
206
+ info('Structural configs that would be merged (new keys only):');
207
+ for (const cfg of STRUCTURAL_CONFIGS) {
208
+ if (existsSync(path.join(cwd, cfg))) {
209
+ const existing = readJson(path.join(cwd, cfg));
210
+ const upstream = existsSync(path.join(PACKAGE_ROOT, cfg))
211
+ ? readJson(path.join(PACKAGE_ROOT, cfg))
212
+ : {};
213
+ const {added} = deepMergeNewKeys(existing, upstream);
214
+ if (added.length > 0) {
215
+ info(` ${cfg}: would add ${added.length} key(s): ${added.join(', ')}`);
216
+ } else {
217
+ info(` ${cfg}: no new keys`);
218
+ }
219
+ }
220
+ }
221
+ console.log('');
222
+
223
+ info('Plugins that would be updated:');
224
+ const pluginsDirSrc = path.join(PACKAGE_ROOT, 'plugins');
225
+ const pluginsDirDst = path.join(cwd, 'plugins');
226
+ if (existsSync(pluginsDirSrc)) {
227
+ const upstreamPlugins = readdirSync(pluginsDirSrc, {withFileTypes: true})
228
+ .filter(e => e.isDirectory())
229
+ .map(e => e.name);
230
+ for (const name of upstreamPlugins) {
231
+ const exists = existsSync(path.join(pluginsDirDst, name));
232
+ info(` → plugins/${name}/ (${exists ? 'update' : 'add'})`);
233
+ }
234
+ }
235
+ console.log('');
236
+
237
+ info('Package.json: CMS deps would be replaced; user-added deps preserved.');
238
+ console.log('');
239
+ process.exit(0);
240
+ }
241
+
242
+ // -------------------------------------------------------------------------
243
+ // 4. Confirmation prompt
244
+ // -------------------------------------------------------------------------
245
+
246
+ if (!skipConfirm) {
247
+ const confirmed = await confirm(`Update project from ${installedVersion} to ${incomingVersion}?`);
248
+ if (!confirmed) {
249
+ info('Update cancelled.');
250
+ console.log('');
251
+ process.exit(0);
252
+ }
253
+ console.log('');
254
+ }
255
+
256
+ // -------------------------------------------------------------------------
257
+ // 5. Backup
258
+ // -------------------------------------------------------------------------
259
+
260
+ let backupPath = null;
261
+
262
+ if (!skipBackup) {
263
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
264
+ backupPath = path.join(cwd, '.domma-backups', timestamp);
265
+ step('Creating backup');
266
+ mkdirSync(backupPath, {recursive: true});
267
+
268
+ // Back up upstream dirs
269
+ for (const dir of UPSTREAM_DIRS) {
270
+ const src = path.join(cwd, dir);
271
+ if (existsSync(src)) {
272
+ cpSync(src, path.join(backupPath, dir), {recursive: true});
273
+ }
274
+ }
275
+
276
+ // Back up package.json and structural configs
277
+ for (const f of ['package.json', ...STRUCTURAL_CONFIGS]) {
278
+ const src = path.join(cwd, f);
279
+ if (existsSync(src)) {
280
+ const dest = path.join(backupPath, f);
281
+ mkdirSync(path.dirname(dest), {recursive: true});
282
+ cpSync(src, dest);
283
+ }
284
+ }
285
+
286
+ // Back up plugins dir (code only, not data)
287
+ const pluginsDir = path.join(cwd, 'plugins');
288
+ if (existsSync(pluginsDir)) {
289
+ cpSync(pluginsDir, path.join(backupPath, 'plugins'), {recursive: true});
290
+ }
291
+
292
+ done();
293
+ } else {
294
+ warn('Skipping backup (--no-backup).');
295
+ }
296
+
297
+ // -------------------------------------------------------------------------
298
+ // 6. Replace upstream directories
299
+ // -------------------------------------------------------------------------
300
+
301
+ step('Replacing upstream code');
302
+ for (const dir of UPSTREAM_DIRS) {
303
+ const dest = path.join(cwd, dir);
304
+ const src = path.join(PACKAGE_ROOT, dir);
305
+ if (!existsSync(src)) continue;
306
+ if (existsSync(dest)) rmSync(dest, {recursive: true, force: true});
307
+ cpSync(src, dest, {recursive: true});
308
+ }
309
+ done();
310
+
311
+ // -------------------------------------------------------------------------
312
+ // 7. Update plugins
313
+ // -------------------------------------------------------------------------
314
+
315
+ step('Updating plugins');
316
+ const pluginsDirSrc = path.join(PACKAGE_ROOT, 'plugins');
317
+ const pluginsDirDst = path.join(cwd, 'plugins');
318
+ const newPlugins = [];
319
+
320
+ if (existsSync(pluginsDirSrc)) {
321
+ mkdirSync(pluginsDirDst, {recursive: true});
322
+ const upstreamPlugins = readdirSync(pluginsDirSrc, {withFileTypes: true})
323
+ .filter(e => e.isDirectory())
324
+ .map(e => e.name);
325
+
326
+ for (const name of upstreamPlugins) {
327
+ const srcPlugin = path.join(pluginsDirSrc, name);
328
+ const dstPlugin = path.join(pluginsDirDst, name);
329
+ const isNew = !existsSync(dstPlugin);
330
+
331
+ if (isNew) {
332
+ // Brand-new plugin — copy wholesale, apply scaffold.reset
333
+ cpSync(srcPlugin, dstPlugin, {recursive: true});
334
+ try {
335
+ const manifest = readJson(path.join(dstPlugin, 'plugin.json'));
336
+ for (const {path: relPath, content} of (manifest.scaffold?.reset ?? [])) {
337
+ const absPath = path.join(dstPlugin, relPath);
338
+ mkdirSync(path.dirname(absPath), {recursive: true});
339
+ writeFileSync(absPath, content + '\n', 'utf8');
340
+ }
341
+ } catch { /* no manifest — skip reset */
342
+ }
343
+ newPlugins.push(name);
344
+ } else {
345
+ // Existing plugin — identify paths to preserve
346
+ let preservePaths = new Set(['data']);
347
+ try {
348
+ const manifest = readJson(path.join(srcPlugin, 'plugin.json'));
349
+ for (const {path: relPath} of (manifest.scaffold?.reset ?? [])) {
350
+ preservePaths.add(relPath);
351
+ }
352
+ } catch { /* no manifest */
353
+ }
354
+
355
+ // Back up preserved paths from current install
356
+ const preserved = new Map();
357
+ for (const rel of preservePaths) {
358
+ const abs = path.join(dstPlugin, rel);
359
+ if (!existsSync(abs)) continue;
360
+
361
+ if (statSync(abs).isDirectory()) {
362
+ // Preserve all files under this directory
363
+ for (const f of listFiles(abs, dstPlugin)) {
364
+ preserved.set(f, readFileSync(path.join(dstPlugin, f)));
365
+ }
366
+ } else {
367
+ // scaffold.reset entry — preserve the single file
368
+ preserved.set(rel, readFileSync(abs));
369
+ }
370
+ }
371
+
372
+ // Replace plugin directory with upstream
373
+ rmSync(dstPlugin, {recursive: true, force: true});
374
+ cpSync(srcPlugin, dstPlugin, {recursive: true});
375
+
376
+ // Restore preserved files
377
+ for (const [rel, content] of preserved) {
378
+ const abs = path.join(dstPlugin, rel);
379
+ mkdirSync(path.dirname(abs), {recursive: true});
380
+ writeFileSync(abs, content);
381
+ }
382
+ }
383
+ }
384
+ }
385
+ done();
386
+
387
+ // -------------------------------------------------------------------------
388
+ // 8. Register new plugins in plugins.json (disabled by default)
389
+ // -------------------------------------------------------------------------
390
+
391
+ const pluginsJsonPath = path.join(cwd, 'config/plugins.json');
392
+ if (newPlugins.length > 0 && existsSync(pluginsJsonPath)) {
393
+ try {
394
+ const pluginsCfg = readJson(pluginsJsonPath);
395
+ for (const name of newPlugins) {
396
+ if (!(name in pluginsCfg)) {
397
+ pluginsCfg[name] = {enabled: false, settings: {}};
398
+ }
399
+ }
400
+ writeJson(pluginsJsonPath, pluginsCfg);
401
+ } catch { /* if plugins.json is malformed, skip */
402
+ }
403
+ }
404
+
405
+ // -------------------------------------------------------------------------
406
+ // 9. Merge structural configs (new keys only)
407
+ // -------------------------------------------------------------------------
408
+
409
+ step('Merging structural configs');
410
+ const mergedKeys = {};
411
+
412
+ for (const cfgFile of STRUCTURAL_CONFIGS) {
413
+ const dstPath = path.join(cwd, cfgFile);
414
+ const srcPath = path.join(PACKAGE_ROOT, cfgFile);
415
+ if (!existsSync(dstPath) || !existsSync(srcPath)) continue;
416
+
417
+ try {
418
+ const existing = readJson(dstPath);
419
+ const upstream = readJson(srcPath);
420
+ const {merged, added} = deepMergeNewKeys(existing, upstream);
421
+ if (added.length > 0) {
422
+ writeJson(dstPath, merged);
423
+ mergedKeys[cfgFile] = added;
424
+ }
425
+ } catch { /* malformed JSON — skip */
426
+ }
427
+ }
428
+ done();
429
+
430
+ // -------------------------------------------------------------------------
431
+ // 10. Update package.json: replace CMS deps, preserve user-added deps
432
+ // -------------------------------------------------------------------------
433
+
434
+ step('Updating package.json');
435
+ const projectPkgPath = path.join(cwd, 'package.json');
436
+ try {
437
+ const projectPkg = readJson(projectPkgPath);
438
+ const cmsDeps = sourcePkg.dependencies ?? {};
439
+ const cmsOptional = sourcePkg.optionalDependencies ?? {};
440
+ const userDeps = projectPkg.dependencies ?? {};
441
+
442
+ // Preserve deps the user added (not in CMS deps at time of scaffold)
443
+ // We identify "user-added" as: present in project but not in the new upstream CMS deps
444
+ const userAddedDeps = {};
445
+ for (const [pkg, ver] of Object.entries(userDeps)) {
446
+ if (!(pkg in cmsDeps) && !(pkg in cmsOptional)) {
447
+ userAddedDeps[pkg] = ver;
448
+ }
449
+ }
450
+
451
+ // Replace CMS scripts, but never copy prepublishOnly
452
+ const newScripts = {...sourcePkg.scripts};
453
+ delete newScripts.prepublishOnly;
454
+
455
+ projectPkg.dependencies = {...cmsDeps, ...userAddedDeps};
456
+ projectPkg.optionalDependencies = cmsOptional;
457
+ projectPkg.scripts = newScripts;
458
+
459
+ writeFileSync(projectPkgPath, JSON.stringify(projectPkg, null, 2) + '\n', 'utf8');
460
+ } catch { /* malformed package.json — skip */
461
+ }
462
+ done();
463
+
464
+ // -------------------------------------------------------------------------
465
+ // 11. Ensure .domma-backups/ is in .gitignore
466
+ // -------------------------------------------------------------------------
467
+
468
+ const gitignorePath = path.join(cwd, '.gitignore');
469
+ const backupEntry = '.domma-backups/';
470
+ if (existsSync(gitignorePath)) {
471
+ const current = readFileSync(gitignorePath, 'utf8');
472
+ if (!current.includes(backupEntry)) {
473
+ writeFileSync(gitignorePath, current.trimEnd() + '\n' + backupEntry + '\n', 'utf8');
474
+ }
475
+ } else {
476
+ writeFileSync(gitignorePath, backupEntry + '\n', 'utf8');
477
+ }
478
+
479
+ // -------------------------------------------------------------------------
480
+ // 12. Write updated .domma marker
481
+ // -------------------------------------------------------------------------
482
+
483
+ step('Updating .domma marker');
484
+ const newMarker = {
485
+ cmsVersion: incomingVersion,
486
+ scaffoldedAt: markerData?.scaffoldedAt ?? null,
487
+ lastUpdatedAt: new Date().toISOString(),
488
+ };
489
+ writeFileSync(markerPath, JSON.stringify(newMarker, null, 2) + '\n', 'utf8');
490
+ done();
491
+
492
+ // -------------------------------------------------------------------------
493
+ // 13. npm install
494
+ // -------------------------------------------------------------------------
495
+
496
+ console.log('');
497
+
498
+ if (skipInstall) {
499
+ warn('Skipping npm install (--no-install).');
500
+ } else {
501
+ info('Running npm install…');
502
+ console.log('');
503
+ const result = spawnSync('npm', ['install'], {cwd, stdio: 'inherit'});
504
+ if (result.status !== 0) {
505
+ console.error('\n ✗ npm install failed.\n');
506
+ process.exit(result.status ?? 1);
507
+ }
508
+ console.log('');
509
+ }
510
+
511
+ // -------------------------------------------------------------------------
512
+ // 14. Summary
513
+ // -------------------------------------------------------------------------
514
+
515
+ console.log(' ┌──────────────────────────────────────────┐');
516
+ console.log(' │ ✓ Update complete! │');
517
+ console.log(' └──────────────────────────────────────────┘');
518
+ console.log('');
519
+
520
+ info(` ${installedVersion} → ${incomingVersion}`);
521
+ console.log('');
522
+
523
+ if (backupPath) {
524
+ info(`Backup saved to: .domma-backups/${path.basename(backupPath)}/`);
525
+ }
526
+
527
+ if (newPlugins.length > 0) {
528
+ info(`New plugins added (disabled by default): ${newPlugins.join(', ')}`);
529
+ }
530
+
531
+ const allMergedKeys = Object.entries(mergedKeys);
532
+ if (allMergedKeys.length > 0) {
533
+ info('New config keys added:');
534
+ for (const [file, keys] of allMergedKeys) {
535
+ info(` ${file}: ${keys.join(', ')}`);
536
+ }
537
+ }
538
+
539
+ console.log('');
540
+ info('Restart your dev server to apply changes: npm run dev');
541
+ console.log('');
542
+ }
@@ -1,6 +1,10 @@
1
- {
2
- "port": 4096,
3
- "host": "0.0.0.0",
4
- "cors": { "origin": false },
5
- "uploads": { "maxFileSize": 10485760 }
6
- }
1
+ {
2
+ "port": 4096,
3
+ "host": "0.0.0.0",
4
+ "cors": {
5
+ "origin": false
6
+ },
7
+ "uploads": {
8
+ "maxFileSize": 10485760
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.5.4",
3
+ "version": "0.6.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",