domma-cms 1.8.25 → 1.8.26
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/CHANGELOG.md +4 -0
- package/bin/lib/backup-retention.js +66 -0
- package/bin/update.js +11 -0
- package/docs/DommaFeatures.md +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.26] - 2026-10-18
|
|
4
|
+
|
|
5
|
+
- **Updates clear out their old backups.** Every `npx domma-cms update` copies the engine and plugins into `.domma-backups/` first, and nothing ever removed those copies - some sites held 250+ of up to 490 MB each. After a successful update it now keeps the newest 7, plus one about a week, a month, three, six and twelve months old (at most 16), and nothing older than a year. Backups you put in that folder yourself, under any other name, are never touched.
|
|
6
|
+
|
|
3
7
|
## [1.8.25] - 2026-10-18
|
|
4
8
|
|
|
5
9
|
- **Tutorial: The Editor.** A walk through a working session in the editor - the list, the tabs, coloured text, the live preview, saving and drafts, and the settings (font, wrapping, smooth typing, the admin menu folding away). Under Documentation › Tutorials, first in the list on every site.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update backups, for the engine updater.
|
|
3
|
+
*
|
|
4
|
+
* Every update copies the engine's code, structural configs and plugins into
|
|
5
|
+
* .domma-backups/<timestamp>/ (about 500 MB on a site with plugins) and nothing
|
|
6
|
+
* ever removed them: a site that rolled a few times a day had 250+ of them. The
|
|
7
|
+
* updater now keeps the newest few plus one backup per age band - about a week,
|
|
8
|
+
* a month, three months, six months and a year old - and nothing older than a
|
|
9
|
+
* year. Only the names the updater writes are counted: anything else in the
|
|
10
|
+
* folder was put there by hand and is left alone.
|
|
11
|
+
*
|
|
12
|
+
* Each band keeps its OLDEST backup - that is what lets one move up: it ages out
|
|
13
|
+
* of its band into the next. Past the first week a band also keeps its NEWEST,
|
|
14
|
+
* the next in line, so when the oldest moves on the band is never left empty
|
|
15
|
+
* (with only the oldest kept, the year band stood empty 44% of the time at three
|
|
16
|
+
* updates a day). So at most 7 + 1 + 2 x 4 = 16 are kept.
|
|
17
|
+
*
|
|
18
|
+
* @module bin/lib/backup-retention
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {existsSync, readdirSync, rmSync} from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
/** How many of the newest update backups a site keeps outright. */
|
|
25
|
+
export const KEEP_UPDATE_BACKUPS = 7;
|
|
26
|
+
|
|
27
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
/** Age bands, in days: [from, to). The first starts at 0 so a backup can survive long enough to reach a week. */
|
|
30
|
+
export const BACKUP_BANDS = [[0, 7], [7, 30], [30, 90], [90, 180], [180, 365]];
|
|
31
|
+
|
|
32
|
+
/** `new Date().toISOString()` with `:` and `.` made `-` - the updater's own names. */
|
|
33
|
+
const UPDATE_BACKUP_NAME = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/;
|
|
34
|
+
|
|
35
|
+
/** When an update backup was taken, from its name; null for a name the updater did not write. */
|
|
36
|
+
export function backupTime(name) {
|
|
37
|
+
const m = UPDATE_BACKUP_NAME.exec(name);
|
|
38
|
+
if (!m) return null;
|
|
39
|
+
const t = Date.parse(`${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`);
|
|
40
|
+
return Number.isNaN(t) ? null : t;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The update backups to remove from a listing, oldest first. Pure: `now` is a timestamp in ms. */
|
|
44
|
+
export function backupsToPrune(names, now = Date.now(), keep = KEEP_UPDATE_BACKUPS) {
|
|
45
|
+
const ours = names
|
|
46
|
+
.map(name => ({name, at: backupTime(name)}))
|
|
47
|
+
.filter(b => b.at !== null)
|
|
48
|
+
.sort((a, b) => a.at - b.at);
|
|
49
|
+
const kept = new Set(ours.slice(-keep).map(b => b.name));
|
|
50
|
+
BACKUP_BANDS.forEach(([from, to], i) => {
|
|
51
|
+
const inBand = ours.filter(b => now - b.at >= from * DAY && now - b.at < to * DAY);
|
|
52
|
+
if (!inBand.length) return;
|
|
53
|
+
kept.add(inBand[0].name);
|
|
54
|
+
if (i > 0) kept.add(inBand.at(-1).name);
|
|
55
|
+
});
|
|
56
|
+
return ours.filter(b => !kept.has(b.name)).map(b => b.name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Apply the retention rule to `dir`. Returns the names removed. */
|
|
60
|
+
export function pruneUpdateBackups(dir, now = Date.now()) {
|
|
61
|
+
if (!existsSync(dir)) return [];
|
|
62
|
+
const names = readdirSync(dir, {withFileTypes: true}).filter(e => e.isDirectory()).map(e => e.name);
|
|
63
|
+
const doomed = backupsToPrune(names, now);
|
|
64
|
+
for (const name of doomed) rmSync(path.join(dir, name), {recursive: true, force: true});
|
|
65
|
+
return doomed;
|
|
66
|
+
}
|
package/bin/update.js
CHANGED
|
@@ -15,6 +15,7 @@ import {createInterface} from 'node:readline';
|
|
|
15
15
|
import {fileURLToPath} from 'node:url';
|
|
16
16
|
import {deepMergeNewKeys} from './lib/config-merge.js';
|
|
17
17
|
import {shouldReplacePlugin} from './lib/plugin-version.js';
|
|
18
|
+
import {KEEP_UPDATE_BACKUPS, pruneUpdateBackups} from './lib/backup-retention.js';
|
|
18
19
|
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Paths
|
|
@@ -567,6 +568,13 @@ export default async function update(_positional, flags) {
|
|
|
567
568
|
console.log('');
|
|
568
569
|
}
|
|
569
570
|
|
|
571
|
+
// -------------------------------------------------------------------------
|
|
572
|
+
// 13b. Thin the update backups (bin/lib/backup-retention.js) - done last, so a failed update
|
|
573
|
+
// never costs a backup, and only when this run took one.
|
|
574
|
+
// -------------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
const prunedBackups = backupPath ? pruneUpdateBackups(path.join(cwd, '.domma-backups')) : [];
|
|
577
|
+
|
|
570
578
|
// -------------------------------------------------------------------------
|
|
571
579
|
// 14. Summary
|
|
572
580
|
// -------------------------------------------------------------------------
|
|
@@ -581,6 +589,9 @@ export default async function update(_positional, flags) {
|
|
|
581
589
|
|
|
582
590
|
if (backupPath) {
|
|
583
591
|
info(`Backup saved to: .domma-backups/${path.basename(backupPath)}/`);
|
|
592
|
+
if (prunedBackups.length) {
|
|
593
|
+
info(`Removed ${prunedBackups.length} older update backup(s): the newest ${KEEP_UPDATE_BACKUPS} are kept, plus one about a week, a month, 3, 6 and 12 months old.`);
|
|
594
|
+
}
|
|
584
595
|
}
|
|
585
596
|
|
|
586
597
|
if (newPlugins.length > 0) {
|
package/docs/DommaFeatures.md
CHANGED
|
@@ -1280,9 +1280,10 @@ The four Tools below were plugins until 0.67.0 and are now part of the CMS. They
|
|
|
1280
1280
|
- Registers new plugins, disabled except the protective ones such as Security.
|
|
1281
1281
|
- Adds only new keys to `server.json`, `auth.json`, `content.json` and `presets.json`.
|
|
1282
1282
|
- Swaps the CMS dependencies but keeps dependencies you added, self-heals `.gitignore`, updates the marker and runs `npm install`.
|
|
1283
|
+
- Thins `.domma-backups/` after a successful update: the newest 7 update backups, plus the oldest and newest backup aged 7-30 days, 30-90 days, 90-180 days and 180-365 days (and the oldest under a week), so one about a week, a month, three, six and twelve months old is always there - at most 16. Nothing older than a year is kept, and backups not named by the updater are never touched (unreleased).
|
|
1283
1284
|
|
|
1284
1285
|
Options: `--yes`, `--no-backup`, `--no-install`, `--dry-run` (preview only), `--force` (reinstall the same version).
|
|
1285
|
-
Where: `bin/update.js`, `bin/lib/config-merge.js`, `bin/lib/plugin-version.js` - Since: 0.6.5.
|
|
1286
|
+
Where: `bin/update.js`, `bin/lib/config-merge.js`, `bin/lib/plugin-version.js`, `bin/lib/backup-retention.js` - Since: 0.6.5.
|
|
1286
1287
|
- **`npm run setup`** - The terminal setup wizard:
|
|
1287
1288
|
1. Generates `JWT_SECRET` and `INSTANCE_SECRET`.
|
|
1288
1289
|
2. Creates the first admin if there are no users (password of at least 8 characters).
|