kandev 0.52.0 → 0.53.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/dist/backup.js +98 -0
- package/dist/dev.js +17 -0
- package/package.json +6 -6
package/dist/backup.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isProductionDb = isProductionDb;
|
|
7
|
+
exports.backupProductionDb = backupProductionDb;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
// Prefix for dev-prod-db automatic snapshots. Distinct from the backend's
|
|
12
|
+
// "kandev-*" auto-snapshots and "manual-*" user snapshots so the families
|
|
13
|
+
// don't interfere with each other's retention policies.
|
|
14
|
+
const BACKUP_PREFIX = "dev-prod-db-";
|
|
15
|
+
const BACKUP_SUFFIX = ".db";
|
|
16
|
+
const MAX_BACKUPS = 5;
|
|
17
|
+
/**
|
|
18
|
+
* Returns true if the given dbPath points to a non-dev database that should
|
|
19
|
+
* be backed up before running dev mode. Dev-isolated databases live under
|
|
20
|
+
* <repo>/.kandev-dev/ and are considered disposable.
|
|
21
|
+
*/
|
|
22
|
+
function isProductionDb(dbPath) {
|
|
23
|
+
const normalized = node_path_1.default.normalize(dbPath);
|
|
24
|
+
const segments = normalized.split(node_path_1.default.sep).filter(Boolean);
|
|
25
|
+
return !segments.includes(".kandev-dev");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Backs up the database at dbPath to <data-dir>/backups/ before dev mode
|
|
29
|
+
* runs against it. Keeps only the newest MAX_BACKUPS snapshots.
|
|
30
|
+
*
|
|
31
|
+
* Returns the path of the created backup, or null if the DB didn't exist
|
|
32
|
+
* or no backup was needed.
|
|
33
|
+
*
|
|
34
|
+
* The optional `homeDir` parameter is exposed for tests so they can redirect
|
|
35
|
+
* the backup location without mocking os.homedir(). The optional `now`
|
|
36
|
+
* parameter lets tests pass an explicit timestamp so back-to-back calls in
|
|
37
|
+
* the same millisecond produce distinct filenames + mtimes without sleeping.
|
|
38
|
+
*/
|
|
39
|
+
function backupProductionDb(dbPath, homeDir, now) {
|
|
40
|
+
if (!node_fs_1.default.existsSync(dbPath)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const root = homeDir ?? node_os_1.default.homedir();
|
|
44
|
+
// Backups are always placed under ~/.kandev/data/backups/ (or the
|
|
45
|
+
// caller-supplied homeDir in tests), even when dbPath points elsewhere.
|
|
46
|
+
// This matches the dev-prod-db default flow; custom KANDEV_DATABASE_PATH
|
|
47
|
+
// values are advanced usage where the user is responsible for backup location.
|
|
48
|
+
const dataDir = node_path_1.default.join(root, ".kandev", "data");
|
|
49
|
+
const backupDir = node_path_1.default.join(dataDir, "backups");
|
|
50
|
+
node_fs_1.default.mkdirSync(backupDir, { recursive: true });
|
|
51
|
+
const stamp = now ?? new Date();
|
|
52
|
+
const ts = stamp.toISOString().replace(/[:.]/g, "");
|
|
53
|
+
const name = `${BACKUP_PREFIX}${ts}${BACKUP_SUFFIX}`;
|
|
54
|
+
const dest = node_path_1.default.join(backupDir, name);
|
|
55
|
+
node_fs_1.default.copyFileSync(dbPath, dest);
|
|
56
|
+
// Stamp both atime + mtime so pruning (which sorts by mtime) is deterministic.
|
|
57
|
+
node_fs_1.default.utimesSync(dest, stamp, stamp);
|
|
58
|
+
pruneBackups(backupDir, MAX_BACKUPS);
|
|
59
|
+
return dest;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Keeps only the `keep` newest dev-prod-db backup files in `dir`, deleting
|
|
63
|
+
* older ones. Non-matching files are left untouched.
|
|
64
|
+
*/
|
|
65
|
+
function pruneBackups(dir, keep) {
|
|
66
|
+
let entries;
|
|
67
|
+
try {
|
|
68
|
+
entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const files = entries
|
|
74
|
+
.filter((e) => e.isFile() && e.name.startsWith(BACKUP_PREFIX) && e.name.endsWith(BACKUP_SUFFIX))
|
|
75
|
+
.map((e) => {
|
|
76
|
+
const fullPath = node_path_1.default.join(dir, e.name);
|
|
77
|
+
try {
|
|
78
|
+
const stat = node_fs_1.default.statSync(fullPath);
|
|
79
|
+
return { path: fullPath, mtime: stat.mtime };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.filter((f) => f !== null);
|
|
86
|
+
if (files.length <= keep) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
files.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
90
|
+
for (const f of files.slice(keep)) {
|
|
91
|
+
try {
|
|
92
|
+
node_fs_1.default.unlinkSync(f.path);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Non-critical: don't fail the launch if one old backup can't be removed.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
package/dist/dev.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.resolveDevBackendEnv = resolveDevBackendEnv;
|
|
|
8
8
|
const node_child_process_1 = require("node:child_process");
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const backup_1 = require("./backup");
|
|
11
12
|
const constants_1 = require("./constants");
|
|
12
13
|
const health_1 = require("./health");
|
|
13
14
|
const kandev_env_1 = require("./kandev-env");
|
|
@@ -17,6 +18,22 @@ const web_1 = require("./web");
|
|
|
17
18
|
async function runDev({ repoRoot, backendPort, webPort }) {
|
|
18
19
|
const ports = await (0, shared_1.pickPorts)(backendPort, webPort);
|
|
19
20
|
const { dbPath, extra } = resolveDevBackendEnv(repoRoot);
|
|
21
|
+
if ((0, backup_1.isProductionDb)(dbPath)) {
|
|
22
|
+
try {
|
|
23
|
+
const backupPath = (0, backup_1.backupProductionDb)(dbPath);
|
|
24
|
+
if (backupPath) {
|
|
25
|
+
const name = node_path_1.default.basename(backupPath);
|
|
26
|
+
console.log(`[kandev] backed up production db → ${name}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
+
// Abort rather than continue: the backup exists precisely to protect the
|
|
32
|
+
// production db before dev mode touches it. Proceeding on failure would
|
|
33
|
+
// remove the safety guarantee that justified introducing this guard.
|
|
34
|
+
throw new Error(`failed to back up production db (${message}); aborting dev startup`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
20
37
|
const backendEnv = (0, shared_1.buildBackendEnv)({ ports, extra });
|
|
21
38
|
const webEnv = (0, shared_1.buildWebEnv)({ ports, debug: true });
|
|
22
39
|
const logLevel = process.env.KANDEV_LOGGING_LEVEL?.trim() || process.env.KANDEV_LOG_LEVEL?.trim() || "info";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kandev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Launcher for Kandev — manage tasks, orchestrate agents, review changes, and ship value",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"npm": ">=7"
|
|
23
23
|
},
|
|
24
24
|
"optionalDependencies": {
|
|
25
|
-
"@kdlbs/runtime-linux-x64": "0.
|
|
26
|
-
"@kdlbs/runtime-linux-arm64": "0.
|
|
27
|
-
"@kdlbs/runtime-darwin-x64": "0.
|
|
28
|
-
"@kdlbs/runtime-darwin-arm64": "0.
|
|
29
|
-
"@kdlbs/runtime-win32-x64": "0.
|
|
25
|
+
"@kdlbs/runtime-linux-x64": "0.53.0",
|
|
26
|
+
"@kdlbs/runtime-linux-arm64": "0.53.0",
|
|
27
|
+
"@kdlbs/runtime-darwin-x64": "0.53.0",
|
|
28
|
+
"@kdlbs/runtime-darwin-arm64": "0.53.0",
|
|
29
|
+
"@kdlbs/runtime-win32-x64": "0.53.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"tar": "^7.5.11",
|