primitive-admin 1.1.0-alpha.4 → 1.1.0-alpha.41
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/README.md +325 -31
- package/assets/skill/skills/primitive-platform/SKILL.md +281 -0
- package/dist/bin/primitive.d.ts +2 -0
- package/dist/bin/primitive.js +268 -16
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.d.ts +2 -0
- package/dist/src/commands/admins.js +123 -18
- package/dist/src/commands/admins.js.map +1 -1
- package/dist/src/commands/analytics.d.ts +2 -0
- package/dist/src/commands/analytics.js +464 -55
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.d.ts +2 -0
- package/dist/src/commands/apps.js +55 -13
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +177 -7
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/blob-buckets.d.ts +2 -0
- package/dist/src/commands/blob-buckets.js +422 -0
- package/dist/src/commands/blob-buckets.js.map +1 -0
- package/dist/src/commands/catalog.d.ts +2 -0
- package/dist/src/commands/catalog.js +34 -35
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collection-type-configs.d.ts +2 -0
- package/dist/src/commands/collection-type-configs.js +178 -0
- package/dist/src/commands/collection-type-configs.js.map +1 -0
- package/dist/src/commands/collections.d.ts +2 -0
- package/dist/src/commands/collections.js +515 -0
- package/dist/src/commands/collections.js.map +1 -0
- package/dist/src/commands/comparisons.d.ts +2 -0
- package/dist/src/commands/comparisons.js +6 -6
- package/dist/src/commands/comparisons.js.map +1 -1
- package/dist/src/commands/cron-triggers.d.ts +2 -0
- package/dist/src/commands/cron-triggers.js +357 -0
- package/dist/src/commands/cron-triggers.js.map +1 -0
- package/dist/src/commands/database-types.d.ts +2 -0
- package/dist/src/commands/database-types.js +471 -0
- package/dist/src/commands/database-types.js.map +1 -0
- package/dist/src/commands/databases.d.ts +2 -0
- package/dist/src/commands/databases.js +1462 -83
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +2 -0
- package/dist/src/commands/documents.js +518 -11
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.d.ts +2 -0
- package/dist/src/commands/email-templates.js +281 -0
- package/dist/src/commands/email-templates.js.map +1 -0
- package/dist/src/commands/env.d.ts +12 -0
- package/dist/src/commands/env.js +260 -0
- package/dist/src/commands/env.js.map +1 -0
- package/dist/src/commands/group-type-configs.d.ts +2 -0
- package/dist/src/commands/group-type-configs.js +181 -0
- package/dist/src/commands/group-type-configs.js.map +1 -0
- package/dist/src/commands/groups.d.ts +2 -0
- package/dist/src/commands/groups.js +48 -45
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.d.ts +159 -0
- package/dist/src/commands/guides.js +449 -35
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/init.d.ts +17 -0
- package/dist/src/commands/init.js +811 -201
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.d.ts +2 -0
- package/dist/src/commands/integrations.js +533 -46
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/llm.d.ts +2 -0
- package/dist/src/commands/llm.js +4 -2
- package/dist/src/commands/llm.js.map +1 -1
- package/dist/src/commands/metadata.d.ts +2 -0
- package/dist/src/commands/metadata.js +194 -0
- package/dist/src/commands/metadata.js.map +1 -0
- package/dist/src/commands/prompts.d.ts +2 -0
- package/dist/src/commands/prompts.js +50 -52
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +3 -0
- package/dist/src/commands/rule-sets.js +388 -0
- package/dist/src/commands/rule-sets.js.map +1 -0
- package/dist/src/commands/scripts.d.ts +2 -0
- package/dist/src/commands/scripts.js +667 -0
- package/dist/src/commands/scripts.js.map +1 -0
- package/dist/src/commands/secrets.d.ts +2 -0
- package/dist/src/commands/secrets.js +108 -0
- package/dist/src/commands/secrets.js.map +1 -0
- package/dist/src/commands/skill.d.ts +2 -0
- package/dist/src/commands/skill.js +29 -0
- package/dist/src/commands/skill.js.map +1 -0
- package/dist/src/commands/sync.d.ts +175 -0
- package/dist/src/commands/sync.js +4412 -233
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.d.ts +2 -0
- package/dist/src/commands/tokens.js +122 -19
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.d.ts +2 -0
- package/dist/src/commands/users.js +440 -22
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/waitlist.d.ts +2 -0
- package/dist/src/commands/waitlist.js +10 -10
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.d.ts +2 -0
- package/dist/src/commands/webhooks.js +391 -0
- package/dist/src/commands/webhooks.js.map +1 -0
- package/dist/src/commands/workflows.d.ts +49 -0
- package/dist/src/commands/workflows.js +1009 -123
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +1283 -0
- package/dist/src/lib/api-client.js +1129 -83
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/auth-flow.d.ts +8 -0
- package/dist/src/lib/block-layout.d.ts +160 -0
- package/dist/src/lib/block-layout.js +451 -0
- package/dist/src/lib/block-layout.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +60 -0
- package/dist/src/lib/cli-manifest.js +70 -0
- package/dist/src/lib/cli-manifest.js.map +1 -0
- package/dist/src/lib/config.d.ts +53 -0
- package/dist/src/lib/config.js +92 -53
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/confirm-prompt.d.ts +83 -0
- package/dist/src/lib/confirm-prompt.js +110 -0
- package/dist/src/lib/confirm-prompt.js.map +1 -0
- package/dist/src/lib/constants.d.ts +2 -0
- package/dist/src/lib/constants.js +3 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/crash-handlers.d.ts +20 -0
- package/dist/src/lib/crash-handlers.js +49 -0
- package/dist/src/lib/crash-handlers.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +79 -0
- package/dist/src/lib/credentials-store.js +307 -0
- package/dist/src/lib/credentials-store.js.map +1 -0
- package/dist/src/lib/csv.d.ts +48 -0
- package/dist/src/lib/csv.js +177 -0
- package/dist/src/lib/csv.js.map +1 -0
- package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
- package/dist/src/lib/db-codegen/dbGenerator.js +371 -0
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
- package/dist/src/lib/db-codegen/dbNaming.js +104 -0
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +159 -0
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js +112 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +62 -0
- package/dist/src/lib/env-resolver.js +121 -0
- package/dist/src/lib/env-resolver.js.map +1 -0
- package/dist/src/lib/fetch.d.ts +5 -0
- package/dist/src/lib/generated-allowlist.d.ts +28 -0
- package/dist/src/lib/generated-allowlist.js +220 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +59 -0
- package/dist/src/lib/init-config.js +113 -0
- package/dist/src/lib/init-config.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +49 -0
- package/dist/src/lib/migration-nag.js +163 -0
- package/dist/src/lib/migration-nag.js.map +1 -0
- package/dist/src/lib/output.d.ts +86 -0
- package/dist/src/lib/output.js +150 -8
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +33 -0
- package/dist/src/lib/paginate.js +42 -0
- package/dist/src/lib/paginate.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +97 -0
- package/dist/src/lib/project-config.js +209 -0
- package/dist/src/lib/project-config.js.map +1 -0
- package/dist/src/lib/query-operators.d.ts +43 -0
- package/dist/src/lib/query-operators.js +80 -0
- package/dist/src/lib/query-operators.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
- package/dist/src/lib/refresh-admin-credentials.js +103 -0
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +45 -0
- package/dist/src/lib/resolve-platform.js +43 -0
- package/dist/src/lib/resolve-platform.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +23 -0
- package/dist/src/lib/skill-installer.js +135 -0
- package/dist/src/lib/skill-installer.js.map +1 -0
- package/dist/src/lib/snapshots.d.ts +99 -0
- package/dist/src/lib/snapshots.js +357 -0
- package/dist/src/lib/snapshots.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +72 -0
- package/dist/src/lib/sync-paths.js +130 -0
- package/dist/src/lib/sync-paths.js.map +1 -0
- package/dist/src/lib/template.d.ts +97 -0
- package/dist/src/lib/template.js +336 -62
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/token-inject.d.ts +56 -0
- package/dist/src/lib/token-inject.js +204 -0
- package/dist/src/lib/token-inject.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +152 -0
- package/dist/src/lib/toml-database-config.js +600 -0
- package/dist/src/lib/toml-database-config.js.map +1 -0
- package/dist/src/lib/toml-metadata-config.d.ts +139 -0
- package/dist/src/lib/toml-metadata-config.js +427 -0
- package/dist/src/lib/toml-metadata-config.js.map +1 -0
- package/dist/src/lib/toml-params-validator.d.ts +129 -0
- package/dist/src/lib/toml-params-validator.js +298 -0
- package/dist/src/lib/toml-params-validator.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +10 -0
- package/dist/src/lib/version-check.js +172 -0
- package/dist/src/lib/version-check.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +66 -0
- package/dist/src/lib/workflow-apply.js +117 -0
- package/dist/src/lib/workflow-apply.js.map +1 -0
- package/dist/src/lib/workflow-fragments.d.ts +41 -0
- package/dist/src/lib/workflow-fragments.js +121 -0
- package/dist/src/lib/workflow-fragments.js.map +1 -0
- package/dist/src/lib/workflow-toml-validator.d.ts +95 -0
- package/dist/src/lib/workflow-toml-validator.js +323 -0
- package/dist/src/lib/workflow-toml-validator.js.map +1 -0
- package/dist/src/types/index.d.ts +520 -0
- package/dist/src/validators.d.ts +64 -0
- package/dist/src/validators.js +63 -0
- package/dist/src/validators.js.map +1 -0
- package/package.json +18 -4
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join, dirname } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
9
|
+
const SKILLS_DIR = join(CLAUDE_DIR, "skills");
|
|
10
|
+
const SKILL_DIR = join(SKILLS_DIR, "primitive-platform");
|
|
11
|
+
const SKILL_FILE = join(SKILL_DIR, "SKILL.md");
|
|
12
|
+
const HASH_FILE = join(SKILL_DIR, ".version");
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
function getBundledSkillPath() {
|
|
15
|
+
// In dist: dist/src/lib/skill-installer.js -> ../../../assets/...
|
|
16
|
+
// In source (tsx): src/lib/skill-installer.ts -> ../../assets/...
|
|
17
|
+
const distPath = fileURLToPath(new URL("../../../assets/skill/skills/primitive-platform/SKILL.md", import.meta.url));
|
|
18
|
+
if (existsSync(distPath))
|
|
19
|
+
return distPath;
|
|
20
|
+
return fileURLToPath(new URL("../../assets/skill/skills/primitive-platform/SKILL.md", import.meta.url));
|
|
21
|
+
}
|
|
22
|
+
function computeHash(content) {
|
|
23
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
24
|
+
}
|
|
25
|
+
function getInstalledHash() {
|
|
26
|
+
try {
|
|
27
|
+
if (!existsSync(HASH_FILE))
|
|
28
|
+
return null;
|
|
29
|
+
return readFileSync(HASH_FILE, "utf-8").trim();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns true if the skill is currently installed.
|
|
37
|
+
*/
|
|
38
|
+
export function isSkillInstalled() {
|
|
39
|
+
return getInstalledHash() !== null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Auto-update an already-installed skill if a newer version is bundled.
|
|
43
|
+
* Called after command execution for non-init, non-skill commands.
|
|
44
|
+
* If the skill is not installed, shows a non-blocking hint instead.
|
|
45
|
+
* Never throws — fails silently like version-check.
|
|
46
|
+
*/
|
|
47
|
+
export async function checkSkillStatus() {
|
|
48
|
+
try {
|
|
49
|
+
const bundledPath = getBundledSkillPath();
|
|
50
|
+
if (!existsSync(bundledPath))
|
|
51
|
+
return;
|
|
52
|
+
if (!existsSync(CLAUDE_DIR))
|
|
53
|
+
return;
|
|
54
|
+
const bundledContent = await readFile(bundledPath, "utf-8");
|
|
55
|
+
const bundledHash = computeHash(bundledContent);
|
|
56
|
+
const installedHash = getInstalledHash();
|
|
57
|
+
if (installedHash) {
|
|
58
|
+
// Already installed — silently auto-update if needed
|
|
59
|
+
if (installedHash === bundledHash)
|
|
60
|
+
return;
|
|
61
|
+
mkdirSync(SKILL_DIR, { recursive: true });
|
|
62
|
+
writeFileSync(SKILL_FILE, bundledContent);
|
|
63
|
+
writeFileSync(HASH_FILE, bundledHash);
|
|
64
|
+
console.log(chalk.dim("Updated Primitive platform skill for Claude Code"));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Not installed — show a non-blocking hint
|
|
68
|
+
console.log(chalk.dim(`Claude Code skill available — run ${chalk.cyan("primitive skill install")} to set up`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Never let skill check affect CLI operation
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Explicitly install or reinstall the skill. Shows output.
|
|
77
|
+
*/
|
|
78
|
+
export function installSkillExplicit() {
|
|
79
|
+
const bundledPath = getBundledSkillPath();
|
|
80
|
+
if (!existsSync(bundledPath)) {
|
|
81
|
+
console.error(chalk.red("Skill assets not found. Reinstall the CLI."));
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
85
|
+
console.error(chalk.red("Claude Code not found (~/.claude does not exist)."));
|
|
86
|
+
console.error(chalk.dim("Install Claude Code first: https://claude.ai/code"));
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const bundledContent = readFileSync(bundledPath, "utf-8");
|
|
90
|
+
const bundledHash = computeHash(bundledContent);
|
|
91
|
+
mkdirSync(SKILL_DIR, { recursive: true });
|
|
92
|
+
writeFileSync(SKILL_FILE, bundledContent);
|
|
93
|
+
writeFileSync(HASH_FILE, bundledHash);
|
|
94
|
+
console.log(chalk.green("Primitive platform skill installed for Claude Code."));
|
|
95
|
+
console.log(chalk.dim(`Location: ${SKILL_FILE}`));
|
|
96
|
+
console.log(chalk.dim(`Version: ${bundledHash}`));
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Uninstall the skill.
|
|
101
|
+
*/
|
|
102
|
+
export function uninstallSkill() {
|
|
103
|
+
if (!existsSync(SKILL_DIR)) {
|
|
104
|
+
console.log(chalk.dim("Skill is not installed."));
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
rmSync(SKILL_DIR, { recursive: true });
|
|
108
|
+
console.log(chalk.green("Primitive platform skill uninstalled."));
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Show current skill installation status.
|
|
113
|
+
*/
|
|
114
|
+
export function skillStatus() {
|
|
115
|
+
const installedHash = getInstalledHash();
|
|
116
|
+
const bundledPath = getBundledSkillPath();
|
|
117
|
+
const bundledHash = existsSync(bundledPath)
|
|
118
|
+
? computeHash(readFileSync(bundledPath, "utf-8"))
|
|
119
|
+
: null;
|
|
120
|
+
if (!installedHash) {
|
|
121
|
+
console.log(chalk.yellow("Skill is not installed."));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(chalk.green("Skill is installed."));
|
|
125
|
+
console.log(chalk.dim(` Location: ${SKILL_FILE}`));
|
|
126
|
+
console.log(chalk.dim(` Version: ${installedHash}`));
|
|
127
|
+
}
|
|
128
|
+
if (bundledHash) {
|
|
129
|
+
console.log(chalk.dim(` Bundled: ${bundledHash}`));
|
|
130
|
+
if (installedHash && installedHash !== bundledHash) {
|
|
131
|
+
console.log(chalk.yellow(" Update available — run: primitive skill install"));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=skill-installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-installer.js","sourceRoot":"","sources":["../../../src/lib/skill-installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync snapshots — point-in-time backups of a sync directory taken before a
|
|
3
|
+
* destructive `sync pull` overwrites local TOML state (issue #578, Phase 1).
|
|
4
|
+
*
|
|
5
|
+
* A snapshot is a full recursive copy of the live sync tree (including
|
|
6
|
+
* `.primitive-sync.json`, the content-hash baseline) into a timestamped slot
|
|
7
|
+
* under the snapshots root:
|
|
8
|
+
*
|
|
9
|
+
* <snapshotsRoot>/<YYYY-MM-DDTHH-MM-SS>/
|
|
10
|
+
* <full pre-pull copy of the sync tree, incl. .primitive-sync.json>
|
|
11
|
+
* .snapshot-complete # integrity marker, written LAST
|
|
12
|
+
* .audit-id # ULID cross-link (Phase 2; absent in P1)
|
|
13
|
+
*
|
|
14
|
+
* The `.snapshot-complete` marker is written last so a partial/interrupted
|
|
15
|
+
* copy is detectable: `restoreSnapshot` refuses any snapshot missing it.
|
|
16
|
+
*
|
|
17
|
+
* Snapshots fail LOUD — `createSnapshot` throws if it can't write a complete
|
|
18
|
+
* snapshot. The caller (sync pull) aborts before touching any local file so we
|
|
19
|
+
* never perform a destructive pull without a recoverable backup.
|
|
20
|
+
*
|
|
21
|
+
* Restore is staged-then-swapped: the snapshot is materialized into a sibling
|
|
22
|
+
* temp dir, then the live `configDir` is atomically replaced, so a mid-restore
|
|
23
|
+
* crash never leaves a half-written tree.
|
|
24
|
+
*/
|
|
25
|
+
/** Marker file written LAST inside a snapshot to prove it completed. */
|
|
26
|
+
export declare const SNAPSHOT_COMPLETE_MARKER = ".snapshot-complete";
|
|
27
|
+
/** Cross-link to the audit entry that produced the snapshot (Phase 2). */
|
|
28
|
+
export declare const SNAPSHOT_AUDIT_ID_MARKER = ".audit-id";
|
|
29
|
+
export interface SnapshotResult {
|
|
30
|
+
/** Snapshot id — the timestamp directory name (YYYY-MM-DDTHH-MM-SS). */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Absolute path to the snapshot directory. */
|
|
33
|
+
path: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SnapshotInfo {
|
|
36
|
+
/** Snapshot id — the timestamp directory name. */
|
|
37
|
+
id: string;
|
|
38
|
+
/** Absolute path to the snapshot directory. */
|
|
39
|
+
path: string;
|
|
40
|
+
/** Whether the `.snapshot-complete` marker is present. */
|
|
41
|
+
complete: boolean;
|
|
42
|
+
/** The audit entry id this snapshot is cross-linked to, if any. */
|
|
43
|
+
auditId: string | null;
|
|
44
|
+
/** Creation time (from the directory mtime). */
|
|
45
|
+
createdAt: Date;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Snapshot the current `syncDir` into a new timestamped slot under
|
|
49
|
+
* `snapshotsRoot`. Returns the created snapshot, or `null` when there's
|
|
50
|
+
* nothing to back up (missing/empty `syncDir`).
|
|
51
|
+
*
|
|
52
|
+
* Fails LOUD: throws if the copy or marker write fails. The partial snapshot
|
|
53
|
+
* dir is best-effort cleaned up before rethrowing so a failed snapshot never
|
|
54
|
+
* leaves a marker-less husk behind.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createSnapshot(syncDir: string, snapshotsRoot: string, opts?: {
|
|
57
|
+
auditId?: string;
|
|
58
|
+
now?: Date;
|
|
59
|
+
}): SnapshotResult | null;
|
|
60
|
+
/**
|
|
61
|
+
* List snapshots under `snapshotsRoot`, newest-first. Returns an empty array
|
|
62
|
+
* if the root doesn't exist. Each entry reports whether it's complete and any
|
|
63
|
+
* cross-linked audit id.
|
|
64
|
+
*/
|
|
65
|
+
export declare function listSnapshots(snapshotsRoot: string): SnapshotInfo[];
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a snapshot by id within `snapshotsRoot`. Accepts either the full
|
|
68
|
+
* timestamp directory name OR a unique prefix of at least `minPrefix`
|
|
69
|
+
* characters (default 8). Returns the matching snapshot, or throws a clear
|
|
70
|
+
* error on no-match / ambiguous-prefix.
|
|
71
|
+
*
|
|
72
|
+
* When `id` is omitted, resolves to the most recent COMPLETE snapshot.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveSnapshot(snapshotsRoot: string, id: string | undefined, minPrefix?: number): SnapshotInfo;
|
|
75
|
+
/**
|
|
76
|
+
* Restore a snapshot into `syncDir`, replacing whatever is there.
|
|
77
|
+
*
|
|
78
|
+
* Refuses to restore a snapshot missing the `.snapshot-complete` marker
|
|
79
|
+
* (partial/corrupted snapshot). The internal marker files (`.snapshot-complete`,
|
|
80
|
+
* `.audit-id`) are NOT copied into the restored tree.
|
|
81
|
+
*
|
|
82
|
+
* Staged-then-swapped: the snapshot is materialized into a sibling temp dir,
|
|
83
|
+
* then `syncDir` is atomically replaced, so a mid-restore crash never leaves a
|
|
84
|
+
* half-written tree.
|
|
85
|
+
*
|
|
86
|
+
* `opts.preserveDir` names a directory (the snapshots root) that lives INSIDE
|
|
87
|
+
* `syncDir` and must survive the full-tree swap — the Fork-4 legacy `--dir`
|
|
88
|
+
* case, where backups sit at `<syncDir>/.snapshots/`. Without preserving it,
|
|
89
|
+
* the swap would wipe the user's entire snapshot history on restore.
|
|
90
|
+
*/
|
|
91
|
+
export declare function restoreSnapshot(snapshotPath: string, syncDir: string, opts?: {
|
|
92
|
+
preserveDir?: string;
|
|
93
|
+
}): void;
|
|
94
|
+
/**
|
|
95
|
+
* Prune snapshots older than `retentionDays` from `snapshotsRoot`. Only the
|
|
96
|
+
* given slot is touched (cross-slot retention is independent — each slot has
|
|
97
|
+
* its own snapshots root). No-op if the root doesn't exist.
|
|
98
|
+
*/
|
|
99
|
+
export declare function pruneSnapshots(snapshotsRoot: string, retentionDays?: number): void;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync snapshots — point-in-time backups of a sync directory taken before a
|
|
3
|
+
* destructive `sync pull` overwrites local TOML state (issue #578, Phase 1).
|
|
4
|
+
*
|
|
5
|
+
* A snapshot is a full recursive copy of the live sync tree (including
|
|
6
|
+
* `.primitive-sync.json`, the content-hash baseline) into a timestamped slot
|
|
7
|
+
* under the snapshots root:
|
|
8
|
+
*
|
|
9
|
+
* <snapshotsRoot>/<YYYY-MM-DDTHH-MM-SS>/
|
|
10
|
+
* <full pre-pull copy of the sync tree, incl. .primitive-sync.json>
|
|
11
|
+
* .snapshot-complete # integrity marker, written LAST
|
|
12
|
+
* .audit-id # ULID cross-link (Phase 2; absent in P1)
|
|
13
|
+
*
|
|
14
|
+
* The `.snapshot-complete` marker is written last so a partial/interrupted
|
|
15
|
+
* copy is detectable: `restoreSnapshot` refuses any snapshot missing it.
|
|
16
|
+
*
|
|
17
|
+
* Snapshots fail LOUD — `createSnapshot` throws if it can't write a complete
|
|
18
|
+
* snapshot. The caller (sync pull) aborts before touching any local file so we
|
|
19
|
+
* never perform a destructive pull without a recoverable backup.
|
|
20
|
+
*
|
|
21
|
+
* Restore is staged-then-swapped: the snapshot is materialized into a sibling
|
|
22
|
+
* temp dir, then the live `configDir` is atomically replaced, so a mid-restore
|
|
23
|
+
* crash never leaves a half-written tree.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, readdirSync, statSync, writeFileSync, readFileSync, cpSync, rmSync, renameSync, mkdirSync, mkdtempSync, } from "fs";
|
|
26
|
+
import { join, dirname, resolve, relative, isAbsolute } from "path";
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if `child` is `parent` or lives somewhere beneath it. Used to
|
|
29
|
+
* detect the legacy `--dir` case where the snapshots root sits INSIDE the sync
|
|
30
|
+
* tree (`<userDir>/.snapshots/`) — we must not copy that subtree into the
|
|
31
|
+
* snapshot, or `cpSync` recurses into itself.
|
|
32
|
+
*/
|
|
33
|
+
function isInside(parent, child) {
|
|
34
|
+
const rel = relative(resolve(parent), resolve(child));
|
|
35
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
36
|
+
}
|
|
37
|
+
/** Marker file written LAST inside a snapshot to prove it completed. */
|
|
38
|
+
export const SNAPSHOT_COMPLETE_MARKER = ".snapshot-complete";
|
|
39
|
+
/** Cross-link to the audit entry that produced the snapshot (Phase 2). */
|
|
40
|
+
export const SNAPSHOT_AUDIT_ID_MARKER = ".audit-id";
|
|
41
|
+
/**
|
|
42
|
+
* Produce the timestamp directory name for a snapshot, e.g.
|
|
43
|
+
* `2026-06-01T17-42-18`. Colons (illegal on Windows, awkward everywhere) are
|
|
44
|
+
* replaced with hyphens; milliseconds and the trailing `Z` are dropped.
|
|
45
|
+
*/
|
|
46
|
+
function snapshotTimestamp(now = new Date()) {
|
|
47
|
+
// 2026-06-01T17:42:18.401Z -> 2026-06-01T17-42-18
|
|
48
|
+
return now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns true if `dir` exists and contains at least one entry. A fresh
|
|
52
|
+
* project (no prior sync dir, or an empty one) has nothing to back up.
|
|
53
|
+
*/
|
|
54
|
+
function dirHasContent(dir) {
|
|
55
|
+
if (!existsSync(dir))
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
return readdirSync(dir).length > 0;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Snapshot the current `syncDir` into a new timestamped slot under
|
|
66
|
+
* `snapshotsRoot`. Returns the created snapshot, or `null` when there's
|
|
67
|
+
* nothing to back up (missing/empty `syncDir`).
|
|
68
|
+
*
|
|
69
|
+
* Fails LOUD: throws if the copy or marker write fails. The partial snapshot
|
|
70
|
+
* dir is best-effort cleaned up before rethrowing so a failed snapshot never
|
|
71
|
+
* leaves a marker-less husk behind.
|
|
72
|
+
*/
|
|
73
|
+
export function createSnapshot(syncDir, snapshotsRoot, opts = {}) {
|
|
74
|
+
if (!dirHasContent(syncDir)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const id = snapshotTimestamp(opts.now);
|
|
78
|
+
const dest = join(snapshotsRoot, id);
|
|
79
|
+
// Guard against an id collision (two pulls within the same second). Append a
|
|
80
|
+
// numeric suffix until we find a free slot so a rapid second pull can't
|
|
81
|
+
// clobber the first snapshot.
|
|
82
|
+
let finalId = id;
|
|
83
|
+
let finalDest = dest;
|
|
84
|
+
let suffix = 1;
|
|
85
|
+
while (existsSync(finalDest)) {
|
|
86
|
+
finalId = `${id}-${suffix}`;
|
|
87
|
+
finalDest = join(snapshotsRoot, finalId);
|
|
88
|
+
suffix += 1;
|
|
89
|
+
}
|
|
90
|
+
// Fork 4: under legacy `--dir`, the snapshots root lives INSIDE the sync
|
|
91
|
+
// tree (`<userDir>/.snapshots/`). Node's cpSync refuses to copy a directory
|
|
92
|
+
// into its own subtree (ERR_FS_CP_EINVAL) even with a filter, so in that
|
|
93
|
+
// case we stage the copy into an external temp dir (excluding the backups
|
|
94
|
+
// subtree) and then move it into place.
|
|
95
|
+
const rootIsInside = isInside(syncDir, snapshotsRoot);
|
|
96
|
+
const snapshotsRootResolved = resolve(snapshotsRoot);
|
|
97
|
+
const filter = rootIsInside
|
|
98
|
+
? (src) => !isInside(snapshotsRootResolved, src)
|
|
99
|
+
: undefined;
|
|
100
|
+
let externalStage = null;
|
|
101
|
+
try {
|
|
102
|
+
mkdirSync(snapshotsRoot, { recursive: true });
|
|
103
|
+
if (rootIsInside) {
|
|
104
|
+
// Copy to a sibling of syncDir first (outside the sync tree, so no
|
|
105
|
+
// self-recursion), then move into the inside-the-tree slot. Staging in
|
|
106
|
+
// the parent keeps the move same-filesystem (avoids cross-device EXDEV
|
|
107
|
+
// that a tmpdir stage could hit).
|
|
108
|
+
const parent = dirname(resolve(syncDir));
|
|
109
|
+
mkdirSync(parent, { recursive: true });
|
|
110
|
+
externalStage = mkdtempSync(join(parent, ".primitive-snapshot-stage-"));
|
|
111
|
+
const stageTree = join(externalStage, "tree");
|
|
112
|
+
cpSync(syncDir, stageTree, {
|
|
113
|
+
recursive: true,
|
|
114
|
+
dereference: false,
|
|
115
|
+
...(filter ? { filter } : {}),
|
|
116
|
+
});
|
|
117
|
+
renameSync(stageTree, finalDest);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Full recursive copy. `dereference: false` keeps symlinks from being
|
|
121
|
+
// silently followed (edge case: symlinks inside the sync tree).
|
|
122
|
+
cpSync(syncDir, finalDest, { recursive: true, dereference: false });
|
|
123
|
+
}
|
|
124
|
+
if (opts.auditId) {
|
|
125
|
+
writeFileSync(join(finalDest, SNAPSHOT_AUDIT_ID_MARKER), opts.auditId);
|
|
126
|
+
}
|
|
127
|
+
// Integrity marker written LAST — its presence proves the copy finished.
|
|
128
|
+
writeFileSync(join(finalDest, SNAPSHOT_COMPLETE_MARKER), new Date().toISOString());
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
// Best-effort cleanup of the partial snapshot so we don't leave an
|
|
132
|
+
// incomplete (marker-less) directory around.
|
|
133
|
+
try {
|
|
134
|
+
rmSync(finalDest, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// ignore cleanup failure
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
if (externalStage) {
|
|
143
|
+
try {
|
|
144
|
+
rmSync(externalStage, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// ignore
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { id: finalId, path: finalDest };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* List snapshots under `snapshotsRoot`, newest-first. Returns an empty array
|
|
155
|
+
* if the root doesn't exist. Each entry reports whether it's complete and any
|
|
156
|
+
* cross-linked audit id.
|
|
157
|
+
*/
|
|
158
|
+
export function listSnapshots(snapshotsRoot) {
|
|
159
|
+
if (!existsSync(snapshotsRoot))
|
|
160
|
+
return [];
|
|
161
|
+
let names;
|
|
162
|
+
try {
|
|
163
|
+
names = readdirSync(snapshotsRoot);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const infos = [];
|
|
169
|
+
for (const name of names) {
|
|
170
|
+
const path = join(snapshotsRoot, name);
|
|
171
|
+
let stat;
|
|
172
|
+
try {
|
|
173
|
+
stat = statSync(path);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!stat.isDirectory())
|
|
179
|
+
continue;
|
|
180
|
+
const complete = existsSync(join(path, SNAPSHOT_COMPLETE_MARKER));
|
|
181
|
+
let auditId = null;
|
|
182
|
+
const auditPath = join(path, SNAPSHOT_AUDIT_ID_MARKER);
|
|
183
|
+
if (existsSync(auditPath)) {
|
|
184
|
+
try {
|
|
185
|
+
auditId = readFileSync(auditPath, "utf-8").trim() || null;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
auditId = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
infos.push({ id: name, path, complete, auditId, createdAt: stat.mtime });
|
|
192
|
+
}
|
|
193
|
+
// Newest first. The id is a sortable timestamp string, so a reverse string
|
|
194
|
+
// sort matches chronological order (and stays correct for the `-N` suffixed
|
|
195
|
+
// collision variants within the same second).
|
|
196
|
+
infos.sort((a, b) => (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
|
|
197
|
+
return infos;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Resolve a snapshot by id within `snapshotsRoot`. Accepts either the full
|
|
201
|
+
* timestamp directory name OR a unique prefix of at least `minPrefix`
|
|
202
|
+
* characters (default 8). Returns the matching snapshot, or throws a clear
|
|
203
|
+
* error on no-match / ambiguous-prefix.
|
|
204
|
+
*
|
|
205
|
+
* When `id` is omitted, resolves to the most recent COMPLETE snapshot.
|
|
206
|
+
*/
|
|
207
|
+
export function resolveSnapshot(snapshotsRoot, id, minPrefix = 8) {
|
|
208
|
+
const all = listSnapshots(snapshotsRoot);
|
|
209
|
+
if (all.length === 0) {
|
|
210
|
+
throw new Error(`No snapshots found in ${snapshotsRoot}`);
|
|
211
|
+
}
|
|
212
|
+
if (!id) {
|
|
213
|
+
const mostRecentComplete = all.find((s) => s.complete);
|
|
214
|
+
if (!mostRecentComplete) {
|
|
215
|
+
throw new Error(`No complete snapshots found in ${snapshotsRoot} (all are missing the ${SNAPSHOT_COMPLETE_MARKER} marker).`);
|
|
216
|
+
}
|
|
217
|
+
return mostRecentComplete;
|
|
218
|
+
}
|
|
219
|
+
// Exact match wins.
|
|
220
|
+
const exact = all.find((s) => s.id === id);
|
|
221
|
+
if (exact)
|
|
222
|
+
return exact;
|
|
223
|
+
// Prefix match (must be unique and >= minPrefix chars).
|
|
224
|
+
if (id.length < minPrefix) {
|
|
225
|
+
throw new Error(`Snapshot id "${id}" is too short — provide at least ${minPrefix} characters or the full timestamp.`);
|
|
226
|
+
}
|
|
227
|
+
const matches = all.filter((s) => s.id.startsWith(id));
|
|
228
|
+
if (matches.length === 0) {
|
|
229
|
+
throw new Error(`No snapshot matches "${id}" in ${snapshotsRoot}`);
|
|
230
|
+
}
|
|
231
|
+
if (matches.length > 1) {
|
|
232
|
+
throw new Error(`Snapshot prefix "${id}" is ambiguous (${matches.length} matches: ${matches
|
|
233
|
+
.map((m) => m.id)
|
|
234
|
+
.join(", ")}). Provide more characters.`);
|
|
235
|
+
}
|
|
236
|
+
return matches[0];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Restore a snapshot into `syncDir`, replacing whatever is there.
|
|
240
|
+
*
|
|
241
|
+
* Refuses to restore a snapshot missing the `.snapshot-complete` marker
|
|
242
|
+
* (partial/corrupted snapshot). The internal marker files (`.snapshot-complete`,
|
|
243
|
+
* `.audit-id`) are NOT copied into the restored tree.
|
|
244
|
+
*
|
|
245
|
+
* Staged-then-swapped: the snapshot is materialized into a sibling temp dir,
|
|
246
|
+
* then `syncDir` is atomically replaced, so a mid-restore crash never leaves a
|
|
247
|
+
* half-written tree.
|
|
248
|
+
*
|
|
249
|
+
* `opts.preserveDir` names a directory (the snapshots root) that lives INSIDE
|
|
250
|
+
* `syncDir` and must survive the full-tree swap — the Fork-4 legacy `--dir`
|
|
251
|
+
* case, where backups sit at `<syncDir>/.snapshots/`. Without preserving it,
|
|
252
|
+
* the swap would wipe the user's entire snapshot history on restore.
|
|
253
|
+
*/
|
|
254
|
+
export function restoreSnapshot(snapshotPath, syncDir, opts = {}) {
|
|
255
|
+
if (!existsSync(snapshotPath)) {
|
|
256
|
+
throw new Error(`Snapshot not found: ${snapshotPath}`);
|
|
257
|
+
}
|
|
258
|
+
if (!existsSync(join(snapshotPath, SNAPSHOT_COMPLETE_MARKER))) {
|
|
259
|
+
throw new Error(`Refusing to restore: snapshot ${snapshotPath} is missing the ${SNAPSHOT_COMPLETE_MARKER} marker (partial/corrupted snapshot).`);
|
|
260
|
+
}
|
|
261
|
+
const parent = dirname(syncDir);
|
|
262
|
+
mkdirSync(parent, { recursive: true });
|
|
263
|
+
const unique = `${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
|
|
264
|
+
const stageDir = join(parent, `.primitive-restore-stage-${unique}`);
|
|
265
|
+
const oldDir = join(parent, `.primitive-restore-old-${unique}`);
|
|
266
|
+
// Determine whether a backups dir living inside syncDir needs preserving.
|
|
267
|
+
const preserve = opts.preserveDir &&
|
|
268
|
+
isInside(syncDir, opts.preserveDir) &&
|
|
269
|
+
!isInside(snapshotPath, opts.preserveDir) // don't preserve a dir that's part of the snapshot
|
|
270
|
+
? resolve(opts.preserveDir)
|
|
271
|
+
: null;
|
|
272
|
+
const preserveStash = preserve
|
|
273
|
+
? join(parent, `.primitive-restore-keep-${unique}`)
|
|
274
|
+
: null;
|
|
275
|
+
try {
|
|
276
|
+
// Materialize the snapshot into the staging dir.
|
|
277
|
+
cpSync(snapshotPath, stageDir, { recursive: true, dereference: false });
|
|
278
|
+
// Drop the snapshot-internal marker files from the restored tree.
|
|
279
|
+
for (const marker of [SNAPSHOT_COMPLETE_MARKER, SNAPSHOT_AUDIT_ID_MARKER]) {
|
|
280
|
+
const markerPath = join(stageDir, marker);
|
|
281
|
+
if (existsSync(markerPath)) {
|
|
282
|
+
rmSync(markerPath, { force: true });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Atomic-ish swap: move the live dir aside, move staging into place, then
|
|
286
|
+
// delete the old dir. Same-filesystem renames are atomic.
|
|
287
|
+
if (existsSync(syncDir)) {
|
|
288
|
+
renameSync(syncDir, oldDir);
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
renameSync(stageDir, syncDir);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
// Swap failed — try to roll the original back so we never leave the
|
|
295
|
+
// user with no sync dir at all.
|
|
296
|
+
if (existsSync(oldDir) && !existsSync(syncDir)) {
|
|
297
|
+
try {
|
|
298
|
+
renameSync(oldDir, syncDir);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// ignore rollback failure; original error is more useful
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
throw err;
|
|
305
|
+
}
|
|
306
|
+
// Re-attach the preserved backups dir (Fork-4 `--dir` mode) into the
|
|
307
|
+
// freshly-restored tree, pulling it out of the displaced original.
|
|
308
|
+
if (preserve && preserveStash) {
|
|
309
|
+
const rel = relative(resolve(syncDir), preserve);
|
|
310
|
+
const fromOld = join(oldDir, rel);
|
|
311
|
+
const intoNew = join(syncDir, rel);
|
|
312
|
+
if (existsSync(fromOld) && !existsSync(intoNew)) {
|
|
313
|
+
mkdirSync(dirname(intoNew), { recursive: true });
|
|
314
|
+
renameSync(fromOld, intoNew);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Success — remove the displaced original.
|
|
318
|
+
if (existsSync(oldDir)) {
|
|
319
|
+
rmSync(oldDir, { recursive: true, force: true });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
// Clean up any leftover staging/old/stash dirs on failure.
|
|
324
|
+
for (const dir of [stageDir, oldDir, preserveStash].filter((d) => Boolean(d))) {
|
|
325
|
+
if (existsSync(dir)) {
|
|
326
|
+
try {
|
|
327
|
+
rmSync(dir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// ignore
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Prune snapshots older than `retentionDays` from `snapshotsRoot`. Only the
|
|
338
|
+
* given slot is touched (cross-slot retention is independent — each slot has
|
|
339
|
+
* its own snapshots root). No-op if the root doesn't exist.
|
|
340
|
+
*/
|
|
341
|
+
export function pruneSnapshots(snapshotsRoot, retentionDays = 28) {
|
|
342
|
+
if (!existsSync(snapshotsRoot))
|
|
343
|
+
return;
|
|
344
|
+
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
345
|
+
for (const info of listSnapshots(snapshotsRoot)) {
|
|
346
|
+
if (info.createdAt.getTime() < cutoff) {
|
|
347
|
+
try {
|
|
348
|
+
rmSync(info.path, { recursive: true, force: true });
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Best-effort prune — a failure to delete one stale snapshot must not
|
|
352
|
+
// abort the pull that triggered the prune.
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=snapshots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../../src/lib/snapshots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,UAAU,EACV,WAAW,EACX,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,MAAM,EACN,MAAM,EACN,UAAU,EACV,SAAS,EACT,WAAW,GACZ,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEpE;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,MAAc,EAAE,KAAa;IAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;AAC7D,0EAA0E;AAC1E,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;AAsBpD;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,MAAY,IAAI,IAAI,EAAE;IAC/C,kDAAkD;IAClD,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrE,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,aAAqB,EACrB,OAAyC,EAAE;IAE3C,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAErC,6EAA6E;IAC7E,wEAAwE;IACxE,8BAA8B;IAC9B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,OAAO,GAAG,GAAG,EAAE,IAAI,MAAM,EAAE,CAAC;QAC5B,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC,CAAC;IACd,CAAC;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,0EAA0E;IAC1E,wCAAwC;IACxC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACtD,MAAM,qBAAqB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,YAAY;QACzB,CAAC,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,qBAAqB,EAAE,GAAG,CAAC;QACxD,CAAC,CAAC,SAAS,CAAC;IAEd,IAAI,aAAa,GAAkB,IAAI,CAAC;IACxC,IAAI,CAAC;QACH,SAAS,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE9C,IAAI,YAAY,EAAE,CAAC;YACjB,mEAAmE;YACnE,uEAAuE;YACvE,uEAAuE;YACvE,kCAAkC;YAClC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;YACzC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,aAAa,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC,CAAC;YACxE,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAC9C,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE;gBACzB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,KAAK;gBAClB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9B,CAAC,CAAC;YACH,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,gEAAgE;YAChE,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACzE,CAAC;QAED,yEAAyE;QACzE,aAAa,CACX,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,EACzC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CACzB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,mEAAmE;QACnE,6CAA6C;QAC7C,IAAI,CAAC;YACH,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,IAAI,aAAa,EAAE,CAAC;YAClB,IAAI,CAAC;gBACH,MAAM,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,aAAqB;IACjD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,CAAC;IAE1C,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YACH,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,SAAS;QAElC,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC;QAClE,IAAI,OAAO,GAAkB,IAAI,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC;QACvD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,OAAO,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;QACH,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,2EAA2E;IAC3E,4EAA4E;IAC5E,8CAA8C;IAC9C,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,aAAqB,EACrB,EAAsB,EACtB,SAAS,GAAG,CAAC;IAEb,MAAM,GAAG,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IACzC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,yBAAyB,aAAa,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,kBAAkB,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,kCAAkC,aAAa,yBAAyB,wBAAwB,WAAW,CAC5G,CAAC;QACJ,CAAC;QACD,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,oBAAoB;IACpB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IAExB,wDAAwD;IACxD,IAAI,EAAE,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,gBAAgB,EAAE,qCAAqC,SAAS,oCAAoC,CACrG,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IACvD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,wBAAwB,EAAE,QAAQ,aAAa,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,oBAAoB,EAAE,mBAAmB,OAAO,CAAC,MAAM,aAAa,OAAO;aACxE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChB,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAC3C,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,OAAe,EACf,OAAiC,EAAE;IAEnC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,uBAAuB,YAAY,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CACb,iCAAiC,YAAY,mBAAmB,wBAAwB,uCAAuC,CAChI,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvC,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,4BAA4B,MAAM,EAAE,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,0BAA0B,MAAM,EAAE,CAAC,CAAC;IAEhE,0EAA0E;IAC1E,MAAM,QAAQ,GACZ,IAAI,CAAC,WAAW;QAChB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC;QACnC,CAAC,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,mDAAmD;QAC3F,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;QAC3B,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,aAAa,GAAG,QAAQ;QAC5B,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,2BAA2B,MAAM,EAAE,CAAC;QACnD,CAAC,CAAC,IAAI,CAAC;IAET,IAAI,CAAC;QACH,iDAAiD;QACjD,MAAM,CAAC,YAAY,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;QACxE,kEAAkE;QAClE,KAAK,MAAM,MAAM,IAAI,CAAC,wBAAwB,EAAE,wBAAwB,CAAC,EAAE,CAAC;YAC1E,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC1C,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3B,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;QAED,0EAA0E;QAC1E,0DAA0D;QAC1D,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC;YACH,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,oEAAoE;YACpE,gCAAgC;YAChC,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/C,IAAI,CAAC;oBACH,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC9B,CAAC;gBAAC,MAAM,CAAC;oBACP,yDAAyD;gBAC3D,CAAC;YACH,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,qEAAqE;QACrE,mEAAmE;QACnE,IAAI,QAAQ,IAAI,aAAa,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;YACjD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACnC,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChD,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjD,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,2CAA2C;QAC3C,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;YAAS,CAAC;QACT,2DAA2D;QAC3D,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,MAAM,CACxD,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAC/B,EAAE,CAAC;YACF,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC;oBACH,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAChD,CAAC;gBAAC,MAAM,CAAC;oBACP,SAAS;gBACX,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,aAAqB,EAAE,aAAa,GAAG,EAAE;IACtE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO;IAEvC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAChE,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;QAChD,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,sEAAsE;gBACtE,2CAA2C;YAC7C,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
|