@lumy-pack/syncpoint 0.0.10 → 0.0.12
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/cli.mjs +847 -363
- package/dist/commands/Link.d.ts +9 -0
- package/dist/commands/Unlink.d.ts +2 -0
- package/dist/core/link.d.ts +19 -0
- package/dist/errors.d.ts +2 -0
- package/dist/index.cjs +44 -15
- package/dist/index.mjs +44 -15
- package/dist/utils/types.d.ts +15 -0
- package/dist/version.d.ts +1 -1
- package/package.json +3 -1
package/dist/cli.mjs
CHANGED
|
@@ -87,9 +87,6 @@ var init_wizard_template = __esm({
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
// src/cli.ts
|
|
91
|
-
import { Command } from "commander";
|
|
92
|
-
|
|
93
90
|
// ../shared/src/respond.ts
|
|
94
91
|
function respond(command, data, startTime, version) {
|
|
95
92
|
const response = {
|
|
@@ -119,6 +116,9 @@ function respondError(command, code, message, startTime, version, details) {
|
|
|
119
116
|
process.exitCode = 1;
|
|
120
117
|
}
|
|
121
118
|
|
|
119
|
+
// src/cli.ts
|
|
120
|
+
import { Command } from "commander";
|
|
121
|
+
|
|
122
122
|
// src/commands/Backup.tsx
|
|
123
123
|
import { Box, Static, Text as Text2, useApp } from "ink";
|
|
124
124
|
import { render } from "ink";
|
|
@@ -148,6 +148,7 @@ var ProgressBar = ({
|
|
|
148
148
|
// src/core/backup.ts
|
|
149
149
|
import { readdir } from "fs/promises";
|
|
150
150
|
import { basename, join as join5 } from "path";
|
|
151
|
+
import { filter } from "@winglet/common-utils";
|
|
151
152
|
import fg from "fast-glob";
|
|
152
153
|
|
|
153
154
|
// src/constants.ts
|
|
@@ -451,6 +452,10 @@ function isValidPattern(pattern) {
|
|
|
451
452
|
// src/core/metadata.ts
|
|
452
453
|
import { createHash } from "crypto";
|
|
453
454
|
import { lstat, readFile, readlink } from "fs/promises";
|
|
455
|
+
import { isString } from "@winglet/common-utils";
|
|
456
|
+
|
|
457
|
+
// src/schemas/metadata.schema.ts
|
|
458
|
+
import { map } from "@winglet/common-utils";
|
|
454
459
|
|
|
455
460
|
// assets/schemas/metadata.schema.json
|
|
456
461
|
var metadata_schema_default = {
|
|
@@ -595,14 +600,15 @@ var validate2 = ajv.compile(metadata_schema_default);
|
|
|
595
600
|
function validateMetadata(data) {
|
|
596
601
|
const valid = validate2(data);
|
|
597
602
|
if (valid) return { valid: true };
|
|
598
|
-
const errors = validate2.errors
|
|
603
|
+
const errors = validate2.errors ? map(
|
|
604
|
+
validate2.errors,
|
|
599
605
|
(e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
|
|
600
|
-
);
|
|
606
|
+
) : void 0;
|
|
601
607
|
return { valid: false, errors };
|
|
602
608
|
}
|
|
603
609
|
|
|
604
610
|
// src/version.ts
|
|
605
|
-
var VERSION = "0.0.
|
|
611
|
+
var VERSION = "0.0.12";
|
|
606
612
|
|
|
607
613
|
// src/core/metadata.ts
|
|
608
614
|
var METADATA_VERSION = "1.0.0";
|
|
@@ -626,7 +632,7 @@ function createMetadata(files, config) {
|
|
|
626
632
|
};
|
|
627
633
|
}
|
|
628
634
|
function parseMetadata(data) {
|
|
629
|
-
const str =
|
|
635
|
+
const str = isString(data) ? data : data.toString("utf-8");
|
|
630
636
|
const parsed = JSON.parse(str);
|
|
631
637
|
const result = validateMetadata(parsed);
|
|
632
638
|
if (!result.valid) {
|
|
@@ -780,7 +786,8 @@ async function scanTargets(config) {
|
|
|
780
786
|
"**/Library/**",
|
|
781
787
|
"**/.cache/**",
|
|
782
788
|
"**/node_modules/**",
|
|
783
|
-
...
|
|
789
|
+
...filter(
|
|
790
|
+
config.backup.exclude,
|
|
784
791
|
(p) => detectPatternType(p) === "glob"
|
|
785
792
|
)
|
|
786
793
|
]
|
|
@@ -951,8 +958,12 @@ async function createBackup(config, options = {}) {
|
|
|
951
958
|
// src/core/config.ts
|
|
952
959
|
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
953
960
|
import { join as join7 } from "path";
|
|
961
|
+
import { isArray, map as map3 } from "@winglet/common-utils";
|
|
954
962
|
import YAML from "yaml";
|
|
955
963
|
|
|
964
|
+
// src/schemas/config.schema.ts
|
|
965
|
+
import { map as map2 } from "@winglet/common-utils";
|
|
966
|
+
|
|
956
967
|
// assets/schemas/config.schema.json
|
|
957
968
|
var config_schema_default = {
|
|
958
969
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -1022,9 +1033,10 @@ var validate3 = ajv.compile(config_schema_default);
|
|
|
1022
1033
|
function validateConfig(data) {
|
|
1023
1034
|
const valid = validate3(data);
|
|
1024
1035
|
if (valid) return { valid: true };
|
|
1025
|
-
const errors = validate3.errors
|
|
1036
|
+
const errors = validate3.errors ? map2(
|
|
1037
|
+
validate3.errors,
|
|
1026
1038
|
(e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
|
|
1027
|
-
);
|
|
1039
|
+
) : void 0;
|
|
1028
1040
|
return { valid: false, errors };
|
|
1029
1041
|
}
|
|
1030
1042
|
|
|
@@ -1032,7 +1044,7 @@ function validateConfig(data) {
|
|
|
1032
1044
|
init_assets();
|
|
1033
1045
|
function stripDangerousKeys(obj) {
|
|
1034
1046
|
if (obj === null || typeof obj !== "object") return obj;
|
|
1035
|
-
if (
|
|
1047
|
+
if (isArray(obj)) return map3(obj, stripDangerousKeys);
|
|
1036
1048
|
const cleaned = {};
|
|
1037
1049
|
for (const [key, value] of Object.entries(obj)) {
|
|
1038
1050
|
if (["__proto__", "constructor", "prototype"].includes(key)) continue;
|
|
@@ -1102,6 +1114,8 @@ var SyncpointErrorCode = {
|
|
|
1102
1114
|
PROVISION_FAILED: "PROVISION_FAILED",
|
|
1103
1115
|
MISSING_ARGUMENT: "MISSING_ARGUMENT",
|
|
1104
1116
|
INVALID_ARGUMENT: "INVALID_ARGUMENT",
|
|
1117
|
+
LINK_FAILED: "LINK_FAILED",
|
|
1118
|
+
UNLINK_FAILED: "UNLINK_FAILED",
|
|
1105
1119
|
UNKNOWN: "UNKNOWN"
|
|
1106
1120
|
};
|
|
1107
1121
|
function classifyError(err) {
|
|
@@ -1118,6 +1132,12 @@ function classifyError(err) {
|
|
|
1118
1132
|
if (msg.includes("Template file not found")) {
|
|
1119
1133
|
return SyncpointErrorCode.TEMPLATE_NOT_FOUND;
|
|
1120
1134
|
}
|
|
1135
|
+
if (msg.includes("not a symlink") || msg.includes('"syncpoint link"')) {
|
|
1136
|
+
return SyncpointErrorCode.UNLINK_FAILED;
|
|
1137
|
+
}
|
|
1138
|
+
if (msg.includes("destination is not set") || msg.includes("cross-device") || msg.includes("EXDEV")) {
|
|
1139
|
+
return SyncpointErrorCode.LINK_FAILED;
|
|
1140
|
+
}
|
|
1121
1141
|
return SyncpointErrorCode.UNKNOWN;
|
|
1122
1142
|
}
|
|
1123
1143
|
|
|
@@ -1267,7 +1287,13 @@ var COMMANDS = {
|
|
|
1267
1287
|
required: false
|
|
1268
1288
|
}
|
|
1269
1289
|
],
|
|
1270
|
-
options: [
|
|
1290
|
+
options: [
|
|
1291
|
+
{
|
|
1292
|
+
flag: "--delete <filename>",
|
|
1293
|
+
description: "Delete item by filename",
|
|
1294
|
+
type: "string"
|
|
1295
|
+
}
|
|
1296
|
+
],
|
|
1271
1297
|
examples: [
|
|
1272
1298
|
"npx @lumy-pack/syncpoint list",
|
|
1273
1299
|
"npx @lumy-pack/syncpoint list backups",
|
|
@@ -1279,7 +1305,11 @@ var COMMANDS = {
|
|
|
1279
1305
|
description: "Show ~/.syncpoint/ status summary and manage cleanup",
|
|
1280
1306
|
usage: "npx @lumy-pack/syncpoint status [options]",
|
|
1281
1307
|
options: [
|
|
1282
|
-
{
|
|
1308
|
+
{
|
|
1309
|
+
flag: "--cleanup",
|
|
1310
|
+
description: "Enter interactive cleanup mode",
|
|
1311
|
+
type: "boolean"
|
|
1312
|
+
}
|
|
1283
1313
|
],
|
|
1284
1314
|
examples: [
|
|
1285
1315
|
"npx @lumy-pack/syncpoint status",
|
|
@@ -1302,6 +1332,38 @@ var COMMANDS = {
|
|
|
1302
1332
|
"npx @lumy-pack/syncpoint migrate --dry-run"
|
|
1303
1333
|
]
|
|
1304
1334
|
},
|
|
1335
|
+
link: {
|
|
1336
|
+
name: "link",
|
|
1337
|
+
description: "Move ~/.syncpoint to backup destination and create a symlink",
|
|
1338
|
+
usage: "npx @lumy-pack/syncpoint link [options]",
|
|
1339
|
+
options: [
|
|
1340
|
+
{
|
|
1341
|
+
flag: "-r, --ref <path>",
|
|
1342
|
+
description: "Adopt <path>/.syncpoint as ~/.syncpoint via symlink",
|
|
1343
|
+
type: "string"
|
|
1344
|
+
}
|
|
1345
|
+
],
|
|
1346
|
+
examples: [
|
|
1347
|
+
"npx @lumy-pack/syncpoint link",
|
|
1348
|
+
"npx @lumy-pack/syncpoint link --ref ~/Dropbox"
|
|
1349
|
+
]
|
|
1350
|
+
},
|
|
1351
|
+
unlink: {
|
|
1352
|
+
name: "unlink",
|
|
1353
|
+
description: "Remove the symlink and restore ~/.syncpoint from the destination copy",
|
|
1354
|
+
usage: "npx @lumy-pack/syncpoint unlink [options]",
|
|
1355
|
+
options: [
|
|
1356
|
+
{
|
|
1357
|
+
flag: "--clean",
|
|
1358
|
+
description: "Also remove the destination copy after restoring",
|
|
1359
|
+
type: "boolean"
|
|
1360
|
+
}
|
|
1361
|
+
],
|
|
1362
|
+
examples: [
|
|
1363
|
+
"npx @lumy-pack/syncpoint unlink",
|
|
1364
|
+
"npx @lumy-pack/syncpoint unlink --clean"
|
|
1365
|
+
]
|
|
1366
|
+
},
|
|
1305
1367
|
help: {
|
|
1306
1368
|
name: "help",
|
|
1307
1369
|
description: "Display help information",
|
|
@@ -1524,6 +1586,9 @@ import Spinner from "ink-spinner";
|
|
|
1524
1586
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1525
1587
|
init_wizard_template();
|
|
1526
1588
|
|
|
1589
|
+
// src/schemas/template.schema.ts
|
|
1590
|
+
import { map as map4 } from "@winglet/common-utils";
|
|
1591
|
+
|
|
1527
1592
|
// assets/schemas/template.schema.json
|
|
1528
1593
|
var template_schema_default = {
|
|
1529
1594
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -1592,9 +1657,10 @@ var validate4 = ajv.compile(template_schema_default);
|
|
|
1592
1657
|
function validateTemplate(data) {
|
|
1593
1658
|
const valid = validate4(data);
|
|
1594
1659
|
if (valid) return { valid: true };
|
|
1595
|
-
const errors = validate4.errors
|
|
1660
|
+
const errors = validate4.errors ? map4(
|
|
1661
|
+
validate4.errors,
|
|
1596
1662
|
(e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
|
|
1597
|
-
);
|
|
1663
|
+
) : void 0;
|
|
1598
1664
|
return { valid: false, errors };
|
|
1599
1665
|
}
|
|
1600
1666
|
|
|
@@ -1604,19 +1670,19 @@ init_assets();
|
|
|
1604
1670
|
// src/utils/claude-code-runner.ts
|
|
1605
1671
|
import { spawn } from "child_process";
|
|
1606
1672
|
async function isClaudeCodeAvailable() {
|
|
1607
|
-
return new Promise((
|
|
1673
|
+
return new Promise((resolve3) => {
|
|
1608
1674
|
const child = spawn("which", ["claude"], { shell: true });
|
|
1609
1675
|
child.on("close", (code) => {
|
|
1610
|
-
|
|
1676
|
+
resolve3(code === 0);
|
|
1611
1677
|
});
|
|
1612
1678
|
child.on("error", () => {
|
|
1613
|
-
|
|
1679
|
+
resolve3(false);
|
|
1614
1680
|
});
|
|
1615
1681
|
});
|
|
1616
1682
|
}
|
|
1617
1683
|
async function invokeClaudeCode(prompt, options) {
|
|
1618
1684
|
const timeout = options?.timeout ?? 12e4;
|
|
1619
|
-
return await new Promise((
|
|
1685
|
+
return await new Promise((resolve3, reject) => {
|
|
1620
1686
|
const args = ["--permission-mode", "acceptEdits", "--model", "sonnet"];
|
|
1621
1687
|
if (options?.sessionId) {
|
|
1622
1688
|
args.push("--session", options.sessionId);
|
|
@@ -1641,13 +1707,13 @@ async function invokeClaudeCode(prompt, options) {
|
|
|
1641
1707
|
child.on("close", (code) => {
|
|
1642
1708
|
clearTimeout(timer);
|
|
1643
1709
|
if (code === 0) {
|
|
1644
|
-
|
|
1710
|
+
resolve3({
|
|
1645
1711
|
success: true,
|
|
1646
1712
|
output: stdout,
|
|
1647
1713
|
sessionId: options?.sessionId
|
|
1648
1714
|
});
|
|
1649
1715
|
} else {
|
|
1650
|
-
|
|
1716
|
+
resolve3({
|
|
1651
1717
|
success: false,
|
|
1652
1718
|
output: stdout,
|
|
1653
1719
|
error: stderr || `Process exited with code ${code}`
|
|
@@ -1667,7 +1733,7 @@ async function resumeClaudeCodeSession(sessionId, prompt, options) {
|
|
|
1667
1733
|
});
|
|
1668
1734
|
}
|
|
1669
1735
|
async function invokeClaudeCodeInteractive(prompt) {
|
|
1670
|
-
return await new Promise((
|
|
1736
|
+
return await new Promise((resolve3, reject) => {
|
|
1671
1737
|
const initialMessage = `${prompt}
|
|
1672
1738
|
|
|
1673
1739
|
IMPORTANT INSTRUCTIONS:
|
|
@@ -1691,7 +1757,7 @@ Start by asking the user about their backup priorities for the home directory st
|
|
|
1691
1757
|
// Share stdin/stdout/stderr with parent process
|
|
1692
1758
|
});
|
|
1693
1759
|
child.on("close", (code) => {
|
|
1694
|
-
|
|
1760
|
+
resolve3({
|
|
1695
1761
|
success: code === 0,
|
|
1696
1762
|
output: ""
|
|
1697
1763
|
// No captured output in interactive mode
|
|
@@ -1704,11 +1770,12 @@ Start by asking the user about their backup priorities for the home directory st
|
|
|
1704
1770
|
}
|
|
1705
1771
|
|
|
1706
1772
|
// src/utils/error-formatter.ts
|
|
1773
|
+
import { map as map5 } from "@winglet/common-utils";
|
|
1707
1774
|
function formatValidationErrors(errors) {
|
|
1708
1775
|
if (errors.length === 0) {
|
|
1709
1776
|
return "No validation errors.";
|
|
1710
1777
|
}
|
|
1711
|
-
const formattedErrors = errors
|
|
1778
|
+
const formattedErrors = map5(errors, (error, index) => {
|
|
1712
1779
|
return `${index + 1}. ${error}`;
|
|
1713
1780
|
});
|
|
1714
1781
|
return `Validation failed with ${errors.length} error(s):
|
|
@@ -2247,11 +2314,10 @@ function registerInitCommand(program2) {
|
|
|
2247
2314
|
});
|
|
2248
2315
|
}
|
|
2249
2316
|
|
|
2250
|
-
// src/commands/
|
|
2251
|
-
import {
|
|
2252
|
-
import { Box as
|
|
2317
|
+
// src/commands/Link.tsx
|
|
2318
|
+
import { lstat as lstat3 } from "fs/promises";
|
|
2319
|
+
import { Box as Box5, Text as Text7, useApp as useApp4 } from "ink";
|
|
2253
2320
|
import { render as render5 } from "ink";
|
|
2254
|
-
import SelectInput from "ink-select-input";
|
|
2255
2321
|
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
2256
2322
|
|
|
2257
2323
|
// src/components/Confirm.tsx
|
|
@@ -2291,9 +2357,298 @@ var Confirm = ({
|
|
|
2291
2357
|
] });
|
|
2292
2358
|
};
|
|
2293
2359
|
|
|
2360
|
+
// src/core/link.ts
|
|
2361
|
+
import {
|
|
2362
|
+
cp,
|
|
2363
|
+
lstat as lstat2,
|
|
2364
|
+
readlink as readlink2,
|
|
2365
|
+
rename,
|
|
2366
|
+
rm as rm2,
|
|
2367
|
+
stat as stat2,
|
|
2368
|
+
symlink,
|
|
2369
|
+
unlink
|
|
2370
|
+
} from "fs/promises";
|
|
2371
|
+
import { basename as basename2, join as join10, resolve as resolve2 } from "path";
|
|
2372
|
+
async function linkSyncpoint(destination, _options = {}) {
|
|
2373
|
+
const appDir = getAppDir();
|
|
2374
|
+
const expandedDest = expandTilde(destination);
|
|
2375
|
+
const targetDir = join10(expandedDest, ".syncpoint");
|
|
2376
|
+
let wasAlreadyLinked = false;
|
|
2377
|
+
let lstats;
|
|
2378
|
+
try {
|
|
2379
|
+
lstats = await lstat2(appDir);
|
|
2380
|
+
} catch {
|
|
2381
|
+
throw new Error(`${appDir} does not exist. Run "syncpoint init" first.`);
|
|
2382
|
+
}
|
|
2383
|
+
if (lstats.isSymbolicLink()) {
|
|
2384
|
+
wasAlreadyLinked = true;
|
|
2385
|
+
const existingTarget = await readlink2(appDir);
|
|
2386
|
+
await unlink(appDir);
|
|
2387
|
+
if (existingTarget !== targetDir) {
|
|
2388
|
+
await rename(existingTarget, appDir);
|
|
2389
|
+
} else {
|
|
2390
|
+
await symlink(targetDir, appDir);
|
|
2391
|
+
return { appDir, targetDir, wasAlreadyLinked };
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
try {
|
|
2395
|
+
await rename(appDir, targetDir);
|
|
2396
|
+
} catch (err) {
|
|
2397
|
+
const isExdev = err instanceof Error && (err.message.includes("EXDEV") || err.message.includes("cross-device"));
|
|
2398
|
+
if (isExdev) {
|
|
2399
|
+
throw new Error(
|
|
2400
|
+
`Cannot move ${appDir} to ${targetDir}: source and destination are on different filesystems. Use a destination on the same filesystem, or manually copy the directory.`
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
throw err;
|
|
2404
|
+
}
|
|
2405
|
+
await symlink(targetDir, appDir);
|
|
2406
|
+
return { appDir, targetDir, wasAlreadyLinked };
|
|
2407
|
+
}
|
|
2408
|
+
async function linkSyncpointByRef(refPath) {
|
|
2409
|
+
const appDir = getAppDir();
|
|
2410
|
+
const absoluteRef = resolve2(expandTilde(refPath));
|
|
2411
|
+
const targetDir = basename2(absoluteRef) === ".syncpoint" ? absoluteRef : join10(absoluteRef, ".syncpoint");
|
|
2412
|
+
let refStats;
|
|
2413
|
+
try {
|
|
2414
|
+
refStats = await stat2(targetDir);
|
|
2415
|
+
} catch {
|
|
2416
|
+
throw new Error(`Reference syncpoint path does not exist: ${targetDir}`);
|
|
2417
|
+
}
|
|
2418
|
+
if (!refStats.isDirectory()) {
|
|
2419
|
+
throw new Error(
|
|
2420
|
+
`Reference syncpoint path is not a directory: ${targetDir}`
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
try {
|
|
2424
|
+
const existing = await lstat2(appDir);
|
|
2425
|
+
if (existing.isSymbolicLink()) {
|
|
2426
|
+
await unlink(appDir);
|
|
2427
|
+
} else {
|
|
2428
|
+
await rm2(appDir, { recursive: true, force: true });
|
|
2429
|
+
}
|
|
2430
|
+
} catch {
|
|
2431
|
+
}
|
|
2432
|
+
await symlink(targetDir, appDir);
|
|
2433
|
+
return { appDir, targetDir, wasAlreadyLinked: false };
|
|
2434
|
+
}
|
|
2435
|
+
async function unlinkSyncpoint(options = {}) {
|
|
2436
|
+
const appDir = getAppDir();
|
|
2437
|
+
let lstats;
|
|
2438
|
+
try {
|
|
2439
|
+
lstats = await lstat2(appDir);
|
|
2440
|
+
} catch {
|
|
2441
|
+
throw new Error(`${appDir} does not exist.`);
|
|
2442
|
+
}
|
|
2443
|
+
if (!lstats.isSymbolicLink()) {
|
|
2444
|
+
throw new Error(`${appDir} is not a symlink. Run "syncpoint link" first.`);
|
|
2445
|
+
}
|
|
2446
|
+
const targetDir = await readlink2(appDir);
|
|
2447
|
+
await unlink(appDir);
|
|
2448
|
+
await cp(targetDir, appDir, { recursive: true });
|
|
2449
|
+
if (options.clean) {
|
|
2450
|
+
await rm2(targetDir, { recursive: true, force: true });
|
|
2451
|
+
}
|
|
2452
|
+
return { appDir, targetDir, cleaned: options.clean ?? false };
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// src/commands/Link.tsx
|
|
2456
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2457
|
+
var LinkView = ({ refPath, yes }) => {
|
|
2458
|
+
const { exit } = useApp4();
|
|
2459
|
+
const [phase, setPhase] = useState5("checking");
|
|
2460
|
+
const [result, setResult] = useState5(null);
|
|
2461
|
+
const [error, setError] = useState5(null);
|
|
2462
|
+
const [existingType, setExistingType] = useState5("directory");
|
|
2463
|
+
useEffect4(() => {
|
|
2464
|
+
(async () => {
|
|
2465
|
+
try {
|
|
2466
|
+
if (!refPath) {
|
|
2467
|
+
setPhase("linking");
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
const appDir = getAppDir();
|
|
2471
|
+
try {
|
|
2472
|
+
const stats = await lstat3(appDir);
|
|
2473
|
+
setExistingType(stats.isSymbolicLink() ? "symlink" : "directory");
|
|
2474
|
+
if (yes) {
|
|
2475
|
+
setPhase("linking");
|
|
2476
|
+
} else {
|
|
2477
|
+
setPhase("confirming");
|
|
2478
|
+
}
|
|
2479
|
+
} catch {
|
|
2480
|
+
setPhase("linking");
|
|
2481
|
+
}
|
|
2482
|
+
} catch (err) {
|
|
2483
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2484
|
+
setPhase("error");
|
|
2485
|
+
setTimeout(() => exit(), 100);
|
|
2486
|
+
}
|
|
2487
|
+
})();
|
|
2488
|
+
}, []);
|
|
2489
|
+
useEffect4(() => {
|
|
2490
|
+
if (phase !== "linking") return;
|
|
2491
|
+
(async () => {
|
|
2492
|
+
try {
|
|
2493
|
+
let linkResult;
|
|
2494
|
+
if (refPath) {
|
|
2495
|
+
linkResult = await linkSyncpointByRef(refPath);
|
|
2496
|
+
} else {
|
|
2497
|
+
const config = await loadConfig();
|
|
2498
|
+
const destination = config.backup.destination;
|
|
2499
|
+
if (!destination) {
|
|
2500
|
+
throw new Error(
|
|
2501
|
+
'backup.destination is not set in config.yml. Set it before running "syncpoint link".'
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
linkResult = await linkSyncpoint(destination);
|
|
2505
|
+
}
|
|
2506
|
+
setResult(linkResult);
|
|
2507
|
+
setPhase("done");
|
|
2508
|
+
setTimeout(() => exit(), 100);
|
|
2509
|
+
} catch (err) {
|
|
2510
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2511
|
+
setPhase("error");
|
|
2512
|
+
setTimeout(() => exit(), 100);
|
|
2513
|
+
}
|
|
2514
|
+
})();
|
|
2515
|
+
}, [phase]);
|
|
2516
|
+
if (phase === "error" || error) {
|
|
2517
|
+
return /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
2518
|
+
"\u2717 Link failed: ",
|
|
2519
|
+
error
|
|
2520
|
+
] }) });
|
|
2521
|
+
}
|
|
2522
|
+
if (phase === "confirming") {
|
|
2523
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
2524
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
|
|
2525
|
+
"\u26A0 ~/.syncpoint already exists as a ",
|
|
2526
|
+
existingType,
|
|
2527
|
+
"."
|
|
2528
|
+
] }),
|
|
2529
|
+
/* @__PURE__ */ jsx7(
|
|
2530
|
+
Confirm,
|
|
2531
|
+
{
|
|
2532
|
+
message: "Overwrite with symlink?",
|
|
2533
|
+
defaultYes: false,
|
|
2534
|
+
onConfirm: (confirmed) => {
|
|
2535
|
+
if (confirmed) {
|
|
2536
|
+
setPhase("linking");
|
|
2537
|
+
} else {
|
|
2538
|
+
setError("Aborted.");
|
|
2539
|
+
setPhase("error");
|
|
2540
|
+
setTimeout(() => exit(), 100);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
)
|
|
2545
|
+
] });
|
|
2546
|
+
}
|
|
2547
|
+
if (phase === "checking" || phase === "linking") {
|
|
2548
|
+
return /* @__PURE__ */ jsx7(Box5, { children: /* @__PURE__ */ jsx7(Text7, { children: "\u25B8 Linking ~/.syncpoint to destination..." }) });
|
|
2549
|
+
}
|
|
2550
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
2551
|
+
/* @__PURE__ */ jsx7(Text7, { color: "green", bold: true, children: "\u2713 Link complete" }),
|
|
2552
|
+
result && /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2553
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
2554
|
+
" ",
|
|
2555
|
+
"From: ",
|
|
2556
|
+
contractTilde(result.appDir)
|
|
2557
|
+
] }),
|
|
2558
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
2559
|
+
" ",
|
|
2560
|
+
"To: ",
|
|
2561
|
+
contractTilde(result.targetDir)
|
|
2562
|
+
] }),
|
|
2563
|
+
result.wasAlreadyLinked && /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
|
|
2564
|
+
" ",
|
|
2565
|
+
"(previous link was removed and re-linked)"
|
|
2566
|
+
] })
|
|
2567
|
+
] })
|
|
2568
|
+
] });
|
|
2569
|
+
};
|
|
2570
|
+
function registerLinkCommand(program2) {
|
|
2571
|
+
const cmdInfo = COMMANDS.link;
|
|
2572
|
+
const cmd = program2.command("link").description(cmdInfo.description);
|
|
2573
|
+
cmd.option(
|
|
2574
|
+
"-r, --ref <path>",
|
|
2575
|
+
"Adopt <path>/.syncpoint as ~/.syncpoint via symlink"
|
|
2576
|
+
);
|
|
2577
|
+
cmd.action(async (opts) => {
|
|
2578
|
+
const globalOpts = program2.opts();
|
|
2579
|
+
const startTime = Date.now();
|
|
2580
|
+
if (globalOpts.json) {
|
|
2581
|
+
try {
|
|
2582
|
+
if (opts.ref) {
|
|
2583
|
+
const appDir = getAppDir();
|
|
2584
|
+
let appDirExists = false;
|
|
2585
|
+
try {
|
|
2586
|
+
await lstat3(appDir);
|
|
2587
|
+
appDirExists = true;
|
|
2588
|
+
} catch {
|
|
2589
|
+
}
|
|
2590
|
+
if (appDirExists && !globalOpts.yes) {
|
|
2591
|
+
respondError(
|
|
2592
|
+
"link",
|
|
2593
|
+
SyncpointErrorCode.LINK_FAILED,
|
|
2594
|
+
"~/.syncpoint already exists. Use --yes to overwrite.",
|
|
2595
|
+
startTime,
|
|
2596
|
+
VERSION
|
|
2597
|
+
);
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
const linkResult = await linkSyncpointByRef(opts.ref);
|
|
2601
|
+
respond(
|
|
2602
|
+
"link",
|
|
2603
|
+
{
|
|
2604
|
+
appDir: linkResult.appDir,
|
|
2605
|
+
targetDir: linkResult.targetDir,
|
|
2606
|
+
wasAlreadyLinked: linkResult.wasAlreadyLinked
|
|
2607
|
+
},
|
|
2608
|
+
startTime,
|
|
2609
|
+
VERSION
|
|
2610
|
+
);
|
|
2611
|
+
} else {
|
|
2612
|
+
const config = await loadConfig();
|
|
2613
|
+
const destination = config.backup.destination;
|
|
2614
|
+
if (!destination) {
|
|
2615
|
+
throw new Error("backup.destination is not set in config.yml.");
|
|
2616
|
+
}
|
|
2617
|
+
const linkResult = await linkSyncpoint(destination);
|
|
2618
|
+
respond(
|
|
2619
|
+
"link",
|
|
2620
|
+
{
|
|
2621
|
+
appDir: linkResult.appDir,
|
|
2622
|
+
targetDir: linkResult.targetDir,
|
|
2623
|
+
wasAlreadyLinked: linkResult.wasAlreadyLinked
|
|
2624
|
+
},
|
|
2625
|
+
startTime,
|
|
2626
|
+
VERSION
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
const code = classifyError(error);
|
|
2631
|
+
respondError("link", code, error.message, startTime, VERSION);
|
|
2632
|
+
}
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
const { waitUntilExit } = render5(
|
|
2636
|
+
/* @__PURE__ */ jsx7(LinkView, { refPath: opts.ref, yes: globalOpts.yes ?? false })
|
|
2637
|
+
);
|
|
2638
|
+
await waitUntilExit();
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// src/commands/List.tsx
|
|
2643
|
+
import { unlinkSync } from "fs";
|
|
2644
|
+
import { Box as Box7, Text as Text9, useApp as useApp5, useInput as useInput2 } from "ink";
|
|
2645
|
+
import { render as render6 } from "ink";
|
|
2646
|
+
import SelectInput from "ink-select-input";
|
|
2647
|
+
import { useEffect as useEffect5, useState as useState6 } from "react";
|
|
2648
|
+
|
|
2294
2649
|
// src/components/Table.tsx
|
|
2295
|
-
import { Box as
|
|
2296
|
-
import { jsx as
|
|
2650
|
+
import { Box as Box6, Text as Text8 } from "ink";
|
|
2651
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2297
2652
|
var Table = ({
|
|
2298
2653
|
headers,
|
|
2299
2654
|
rows,
|
|
@@ -2310,13 +2665,13 @@ var Table = ({
|
|
|
2310
2665
|
return text.padEnd(width);
|
|
2311
2666
|
};
|
|
2312
2667
|
const separator = widths.map((w) => "\u2500".repeat(w)).join(" ");
|
|
2313
|
-
return /* @__PURE__ */
|
|
2314
|
-
/* @__PURE__ */
|
|
2668
|
+
return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
|
|
2669
|
+
/* @__PURE__ */ jsx8(Text8, { children: headers.map((h, i) => /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2315
2670
|
padCell(h, widths[i]),
|
|
2316
2671
|
i < headers.length - 1 ? " " : ""
|
|
2317
2672
|
] }, i)) }),
|
|
2318
|
-
/* @__PURE__ */
|
|
2319
|
-
rows.map((row, rowIdx) => /* @__PURE__ */
|
|
2673
|
+
/* @__PURE__ */ jsx8(Text8, { color: "gray", children: separator }),
|
|
2674
|
+
rows.map((row, rowIdx) => /* @__PURE__ */ jsx8(Text8, { children: row.map((cell, colIdx) => /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2320
2675
|
padCell(cell, widths[colIdx]),
|
|
2321
2676
|
colIdx < row.length - 1 ? " " : ""
|
|
2322
2677
|
] }, colIdx)) }, rowIdx))
|
|
@@ -2326,7 +2681,8 @@ var Table = ({
|
|
|
2326
2681
|
// src/core/provision.ts
|
|
2327
2682
|
import { exec } from "child_process";
|
|
2328
2683
|
import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
|
|
2329
|
-
import { join as
|
|
2684
|
+
import { join as join11 } from "path";
|
|
2685
|
+
import { filter as filter2, isTruthy } from "@winglet/common-utils";
|
|
2330
2686
|
import YAML3 from "yaml";
|
|
2331
2687
|
var REMOTE_SCRIPT_PATTERNS = [
|
|
2332
2688
|
/curl\s.*\|\s*(ba)?sh/,
|
|
@@ -2366,7 +2722,7 @@ async function listTemplates() {
|
|
|
2366
2722
|
if (!entry.isFile() || !entry.name.endsWith(".yml") && !entry.name.endsWith(".yaml")) {
|
|
2367
2723
|
continue;
|
|
2368
2724
|
}
|
|
2369
|
-
const fullPath =
|
|
2725
|
+
const fullPath = join11(templatesDir, entry.name);
|
|
2370
2726
|
try {
|
|
2371
2727
|
const config = await loadTemplate(fullPath);
|
|
2372
2728
|
templates.push({
|
|
@@ -2381,7 +2737,7 @@ async function listTemplates() {
|
|
|
2381
2737
|
return templates;
|
|
2382
2738
|
}
|
|
2383
2739
|
function execAsync(command) {
|
|
2384
|
-
return new Promise((
|
|
2740
|
+
return new Promise((resolve3, reject) => {
|
|
2385
2741
|
exec(
|
|
2386
2742
|
command,
|
|
2387
2743
|
{ shell: "/bin/bash", timeout: 3e5 },
|
|
@@ -2394,7 +2750,7 @@ function execAsync(command) {
|
|
|
2394
2750
|
})
|
|
2395
2751
|
);
|
|
2396
2752
|
} else {
|
|
2397
|
-
|
|
2753
|
+
resolve3({
|
|
2398
2754
|
stdout: stdout?.toString() ?? "",
|
|
2399
2755
|
stderr: stderr?.toString() ?? ""
|
|
2400
2756
|
});
|
|
@@ -2435,7 +2791,7 @@ async function executeStep(step) {
|
|
|
2435
2791
|
}
|
|
2436
2792
|
try {
|
|
2437
2793
|
const { stdout, stderr } = await execAsync(step.command);
|
|
2438
|
-
const output = [stdout, stderr]
|
|
2794
|
+
const output = filter2([stdout, stderr], isTruthy).join("\n").trim();
|
|
2439
2795
|
return {
|
|
2440
2796
|
name: step.name,
|
|
2441
2797
|
status: "success",
|
|
@@ -2446,7 +2802,7 @@ async function executeStep(step) {
|
|
|
2446
2802
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
2447
2803
|
const stdout = err?.stdout ?? "";
|
|
2448
2804
|
const stderr = err?.stderr ?? "";
|
|
2449
|
-
const errorOutput = [stdout, stderr, error.message]
|
|
2805
|
+
const errorOutput = filter2([stdout, stderr, error.message], isTruthy).join("\n").trim();
|
|
2450
2806
|
return {
|
|
2451
2807
|
name: step.name,
|
|
2452
2808
|
status: "failed",
|
|
@@ -2484,8 +2840,9 @@ async function* runProvision(templatePath, options = {}) {
|
|
|
2484
2840
|
}
|
|
2485
2841
|
|
|
2486
2842
|
// src/core/restore.ts
|
|
2487
|
-
import { copyFile, lstat as
|
|
2488
|
-
import { dirname as dirname2, join as
|
|
2843
|
+
import { copyFile, lstat as lstat4, readdir as readdir3, stat as stat3 } from "fs/promises";
|
|
2844
|
+
import { dirname as dirname2, join as join12 } from "path";
|
|
2845
|
+
import { forEach } from "@winglet/common-utils";
|
|
2489
2846
|
async function getBackupList(config) {
|
|
2490
2847
|
const backupDir = config?.backup.destination ? resolveTargetPath(config.backup.destination) : getSubDir(BACKUPS_DIR);
|
|
2491
2848
|
const exists = await fileExists(backupDir);
|
|
@@ -2494,8 +2851,8 @@ async function getBackupList(config) {
|
|
|
2494
2851
|
const backups = [];
|
|
2495
2852
|
for (const entry of entries) {
|
|
2496
2853
|
if (!entry.isFile() || !entry.name.endsWith(".tar.gz")) continue;
|
|
2497
|
-
const fullPath =
|
|
2498
|
-
const fileStat = await
|
|
2854
|
+
const fullPath = join12(backupDir, entry.name);
|
|
2855
|
+
const fileStat = await stat3(fullPath);
|
|
2499
2856
|
let hostname;
|
|
2500
2857
|
let fileCount;
|
|
2501
2858
|
try {
|
|
@@ -2543,7 +2900,7 @@ This may not be a valid syncpoint backup.`
|
|
|
2543
2900
|
continue;
|
|
2544
2901
|
}
|
|
2545
2902
|
const currentHash = await computeFileHash(absPath);
|
|
2546
|
-
const currentStat = await
|
|
2903
|
+
const currentStat = await stat3(absPath);
|
|
2547
2904
|
if (currentHash === file.hash) {
|
|
2548
2905
|
actions.push({
|
|
2549
2906
|
path: file.path,
|
|
@@ -2569,7 +2926,7 @@ async function createSafetyBackup(filePaths) {
|
|
|
2569
2926
|
const filename = `_pre-restore_${formatDatetime(now)}.tar.gz`;
|
|
2570
2927
|
const backupDir = getSubDir(BACKUPS_DIR);
|
|
2571
2928
|
await ensureDir(backupDir);
|
|
2572
|
-
const archivePath =
|
|
2929
|
+
const archivePath = join12(backupDir, filename);
|
|
2573
2930
|
const files = [];
|
|
2574
2931
|
for (const fp of filePaths) {
|
|
2575
2932
|
const absPath = resolveTargetPath(fp);
|
|
@@ -2590,21 +2947,32 @@ async function restoreBackup(archivePath, options = {}) {
|
|
|
2590
2947
|
const plan = await getRestorePlan(archivePath);
|
|
2591
2948
|
const restoredFiles = [];
|
|
2592
2949
|
const skippedFiles = [];
|
|
2593
|
-
const overwritePaths =
|
|
2950
|
+
const overwritePaths = [];
|
|
2951
|
+
forEach(plan.actions, (a) => {
|
|
2952
|
+
if (a.action === "overwrite") overwritePaths.push(a.path);
|
|
2953
|
+
});
|
|
2594
2954
|
let safetyBackupPath;
|
|
2595
2955
|
if (overwritePaths.length > 0 && !options.dryRun) {
|
|
2596
2956
|
safetyBackupPath = await createSafetyBackup(overwritePaths);
|
|
2597
2957
|
}
|
|
2598
2958
|
if (options.dryRun) {
|
|
2959
|
+
const restoredFiles2 = [];
|
|
2960
|
+
forEach(plan.actions, (a) => {
|
|
2961
|
+
if (a.action !== "skip") restoredFiles2.push(a.path);
|
|
2962
|
+
});
|
|
2963
|
+
const skippedFiles2 = [];
|
|
2964
|
+
forEach(plan.actions, (a) => {
|
|
2965
|
+
if (a.action === "skip") skippedFiles2.push(a.path);
|
|
2966
|
+
});
|
|
2599
2967
|
return {
|
|
2600
|
-
restoredFiles:
|
|
2601
|
-
skippedFiles:
|
|
2968
|
+
restoredFiles: restoredFiles2,
|
|
2969
|
+
skippedFiles: skippedFiles2,
|
|
2602
2970
|
safetyBackupPath
|
|
2603
2971
|
};
|
|
2604
2972
|
}
|
|
2605
|
-
const { mkdtemp: mkdtemp2, rm:
|
|
2973
|
+
const { mkdtemp: mkdtemp2, rm: rm3 } = await import("fs/promises");
|
|
2606
2974
|
const { tmpdir: tmpdir2 } = await import("os");
|
|
2607
|
-
const tmpDir = await mkdtemp2(
|
|
2975
|
+
const tmpDir = await mkdtemp2(join12(tmpdir2(), "syncpoint-restore-"));
|
|
2608
2976
|
try {
|
|
2609
2977
|
await extractArchive(archivePath, tmpDir);
|
|
2610
2978
|
for (const action of plan.actions) {
|
|
@@ -2613,7 +2981,7 @@ async function restoreBackup(archivePath, options = {}) {
|
|
|
2613
2981
|
continue;
|
|
2614
2982
|
}
|
|
2615
2983
|
const archiveName = action.path.startsWith("~/") ? action.path.slice(2) : action.path;
|
|
2616
|
-
const extractedPath =
|
|
2984
|
+
const extractedPath = join12(tmpDir, archiveName);
|
|
2617
2985
|
const destPath = resolveTargetPath(action.path);
|
|
2618
2986
|
const extractedExists = await fileExists(extractedPath);
|
|
2619
2987
|
if (!extractedExists) {
|
|
@@ -2623,7 +2991,7 @@ async function restoreBackup(archivePath, options = {}) {
|
|
|
2623
2991
|
}
|
|
2624
2992
|
await ensureDir(dirname2(destPath));
|
|
2625
2993
|
try {
|
|
2626
|
-
const destStat = await
|
|
2994
|
+
const destStat = await lstat4(destPath);
|
|
2627
2995
|
if (destStat.isSymbolicLink()) {
|
|
2628
2996
|
logger.warn(`Skipping symlink target: ${action.path}`);
|
|
2629
2997
|
skippedFiles.push(action.path);
|
|
@@ -2636,45 +3004,45 @@ async function restoreBackup(archivePath, options = {}) {
|
|
|
2636
3004
|
restoredFiles.push(action.path);
|
|
2637
3005
|
}
|
|
2638
3006
|
} finally {
|
|
2639
|
-
await
|
|
3007
|
+
await rm3(tmpDir, { recursive: true, force: true });
|
|
2640
3008
|
}
|
|
2641
3009
|
return { restoredFiles, skippedFiles, safetyBackupPath };
|
|
2642
3010
|
}
|
|
2643
3011
|
|
|
2644
3012
|
// src/commands/List.tsx
|
|
2645
|
-
import { jsx as
|
|
3013
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2646
3014
|
var MenuItem = ({ isSelected = false, label }) => {
|
|
2647
3015
|
if (label === "Delete") {
|
|
2648
|
-
return /* @__PURE__ */
|
|
3016
|
+
return /* @__PURE__ */ jsx9(Text9, { bold: isSelected, color: "red", children: label });
|
|
2649
3017
|
}
|
|
2650
3018
|
const match = label.match(/^(.+?)\s+\((\d+)\)$/);
|
|
2651
3019
|
if (match) {
|
|
2652
3020
|
const [, name, count] = match;
|
|
2653
|
-
return /* @__PURE__ */
|
|
2654
|
-
/* @__PURE__ */
|
|
3021
|
+
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3022
|
+
/* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: name }),
|
|
2655
3023
|
" ",
|
|
2656
|
-
/* @__PURE__ */
|
|
3024
|
+
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2657
3025
|
"(",
|
|
2658
3026
|
count,
|
|
2659
3027
|
")"
|
|
2660
3028
|
] })
|
|
2661
3029
|
] });
|
|
2662
3030
|
}
|
|
2663
|
-
return /* @__PURE__ */
|
|
3031
|
+
return /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: label });
|
|
2664
3032
|
};
|
|
2665
3033
|
var ListView = ({ type, deleteIndex }) => {
|
|
2666
|
-
const { exit } =
|
|
2667
|
-
const [phase, setPhase] =
|
|
2668
|
-
const [backups, setBackups] =
|
|
2669
|
-
const [templates, setTemplates] =
|
|
2670
|
-
const [selectedTemplate, setSelectedTemplate] =
|
|
3034
|
+
const { exit } = useApp5();
|
|
3035
|
+
const [phase, setPhase] = useState6("loading");
|
|
3036
|
+
const [backups, setBackups] = useState6([]);
|
|
3037
|
+
const [templates, setTemplates] = useState6([]);
|
|
3038
|
+
const [selectedTemplate, setSelectedTemplate] = useState6(
|
|
2671
3039
|
null
|
|
2672
3040
|
);
|
|
2673
|
-
const [selectedBackup, setSelectedBackup] =
|
|
2674
|
-
const [deleteTarget, setDeleteTarget] =
|
|
2675
|
-
const [error, setError] =
|
|
2676
|
-
const [backupDir, setBackupDir] =
|
|
2677
|
-
const [successMessage, setSuccessMessage] =
|
|
3041
|
+
const [selectedBackup, setSelectedBackup] = useState6(null);
|
|
3042
|
+
const [deleteTarget, setDeleteTarget] = useState6(null);
|
|
3043
|
+
const [error, setError] = useState6(null);
|
|
3044
|
+
const [backupDir, setBackupDir] = useState6(getSubDir("backups"));
|
|
3045
|
+
const [successMessage, setSuccessMessage] = useState6(null);
|
|
2678
3046
|
useInput2((_input, key) => {
|
|
2679
3047
|
if (!key.escape) return;
|
|
2680
3048
|
switch (phase) {
|
|
@@ -2698,7 +3066,7 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2698
3066
|
break;
|
|
2699
3067
|
}
|
|
2700
3068
|
});
|
|
2701
|
-
|
|
3069
|
+
useEffect5(() => {
|
|
2702
3070
|
(async () => {
|
|
2703
3071
|
try {
|
|
2704
3072
|
const config = await loadConfig();
|
|
@@ -2790,16 +3158,16 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2790
3158
|
}
|
|
2791
3159
|
};
|
|
2792
3160
|
if (phase === "error" || error) {
|
|
2793
|
-
return /* @__PURE__ */
|
|
3161
|
+
return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
|
|
2794
3162
|
"\u2717 ",
|
|
2795
3163
|
error
|
|
2796
3164
|
] }) });
|
|
2797
3165
|
}
|
|
2798
3166
|
if (phase === "loading") {
|
|
2799
|
-
return /* @__PURE__ */
|
|
3167
|
+
return /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "Loading..." });
|
|
2800
3168
|
}
|
|
2801
3169
|
if (phase === "deleting" && deleteTarget) {
|
|
2802
|
-
return /* @__PURE__ */
|
|
3170
|
+
return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx9(
|
|
2803
3171
|
Confirm,
|
|
2804
3172
|
{
|
|
2805
3173
|
message: `Delete ${deleteTarget.name}?`,
|
|
@@ -2809,7 +3177,7 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2809
3177
|
) });
|
|
2810
3178
|
}
|
|
2811
3179
|
if (phase === "done" && deleteTarget) {
|
|
2812
|
-
return /* @__PURE__ */
|
|
3180
|
+
return /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
|
|
2813
3181
|
"\u2713 ",
|
|
2814
3182
|
deleteTarget.name,
|
|
2815
3183
|
" deleted"
|
|
@@ -2841,8 +3209,8 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2841
3209
|
setPhase("template-list");
|
|
2842
3210
|
}
|
|
2843
3211
|
};
|
|
2844
|
-
return /* @__PURE__ */
|
|
2845
|
-
/* @__PURE__ */
|
|
3212
|
+
return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
|
|
3213
|
+
/* @__PURE__ */ jsx9(
|
|
2846
3214
|
SelectInput,
|
|
2847
3215
|
{
|
|
2848
3216
|
items: menuItems,
|
|
@@ -2850,9 +3218,9 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2850
3218
|
itemComponent: MenuItem
|
|
2851
3219
|
}
|
|
2852
3220
|
),
|
|
2853
|
-
/* @__PURE__ */
|
|
3221
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2854
3222
|
"Press ",
|
|
2855
|
-
/* @__PURE__ */
|
|
3223
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
|
|
2856
3224
|
" to exit"
|
|
2857
3225
|
] }) })
|
|
2858
3226
|
] });
|
|
@@ -2869,10 +3237,10 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2869
3237
|
setPhase("backup-detail");
|
|
2870
3238
|
}
|
|
2871
3239
|
};
|
|
2872
|
-
return /* @__PURE__ */
|
|
2873
|
-
successMessage && /* @__PURE__ */
|
|
2874
|
-
/* @__PURE__ */
|
|
2875
|
-
/* @__PURE__ */
|
|
3240
|
+
return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
|
|
3241
|
+
successMessage && /* @__PURE__ */ jsx9(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "green", children: successMessage }) }),
|
|
3242
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "\u25B8 Backups" }),
|
|
3243
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: backups.length === 0 ? /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " No backups found." }) : /* @__PURE__ */ jsx9(
|
|
2876
3244
|
SelectInput,
|
|
2877
3245
|
{
|
|
2878
3246
|
items,
|
|
@@ -2880,9 +3248,9 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2880
3248
|
itemComponent: MenuItem
|
|
2881
3249
|
}
|
|
2882
3250
|
) }),
|
|
2883
|
-
/* @__PURE__ */
|
|
3251
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2884
3252
|
"Press ",
|
|
2885
|
-
/* @__PURE__ */
|
|
3253
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
|
|
2886
3254
|
" to go back"
|
|
2887
3255
|
] }) })
|
|
2888
3256
|
] });
|
|
@@ -2899,9 +3267,9 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2899
3267
|
setPhase("template-detail");
|
|
2900
3268
|
}
|
|
2901
3269
|
};
|
|
2902
|
-
return /* @__PURE__ */
|
|
2903
|
-
/* @__PURE__ */
|
|
2904
|
-
/* @__PURE__ */
|
|
3270
|
+
return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
|
|
3271
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "\u25B8 Templates" }),
|
|
3272
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: templates.length === 0 ? /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " No templates found." }) : /* @__PURE__ */ jsx9(
|
|
2905
3273
|
SelectInput,
|
|
2906
3274
|
{
|
|
2907
3275
|
items,
|
|
@@ -2909,9 +3277,9 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2909
3277
|
itemComponent: MenuItem
|
|
2910
3278
|
}
|
|
2911
3279
|
) }),
|
|
2912
|
-
/* @__PURE__ */
|
|
3280
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2913
3281
|
"Press ",
|
|
2914
|
-
/* @__PURE__ */
|
|
3282
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
|
|
2915
3283
|
" to go back"
|
|
2916
3284
|
] }) })
|
|
2917
3285
|
] });
|
|
@@ -2939,20 +3307,20 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2939
3307
|
goBackToBackupList();
|
|
2940
3308
|
}
|
|
2941
3309
|
};
|
|
2942
|
-
return /* @__PURE__ */
|
|
2943
|
-
/* @__PURE__ */
|
|
3310
|
+
return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
|
|
3311
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, children: [
|
|
2944
3312
|
"\u25B8 ",
|
|
2945
3313
|
selectedBackup.filename.replace(".tar.gz", "")
|
|
2946
3314
|
] }),
|
|
2947
|
-
/* @__PURE__ */
|
|
3315
|
+
/* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: sections.map((section, idx) => {
|
|
2948
3316
|
const labelWidth = Math.max(...sections.map((s) => s.label.length)) + 1;
|
|
2949
|
-
return /* @__PURE__ */
|
|
2950
|
-
/* @__PURE__ */
|
|
2951
|
-
/* @__PURE__ */
|
|
2952
|
-
/* @__PURE__ */
|
|
3317
|
+
return /* @__PURE__ */ jsxs9(Box7, { children: [
|
|
3318
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: section.label.padEnd(labelWidth) }),
|
|
3319
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
3320
|
+
/* @__PURE__ */ jsx9(Text9, { children: section.value })
|
|
2953
3321
|
] }, idx);
|
|
2954
3322
|
}) }),
|
|
2955
|
-
/* @__PURE__ */
|
|
3323
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(
|
|
2956
3324
|
SelectInput,
|
|
2957
3325
|
{
|
|
2958
3326
|
items: actionItems,
|
|
@@ -2960,9 +3328,9 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2960
3328
|
itemComponent: MenuItem
|
|
2961
3329
|
}
|
|
2962
3330
|
) }),
|
|
2963
|
-
/* @__PURE__ */
|
|
3331
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2964
3332
|
"Press ",
|
|
2965
|
-
/* @__PURE__ */
|
|
3333
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
|
|
2966
3334
|
" to go back"
|
|
2967
3335
|
] }) })
|
|
2968
3336
|
] });
|
|
@@ -2982,19 +3350,19 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2982
3350
|
}
|
|
2983
3351
|
};
|
|
2984
3352
|
const labelWidth = Math.max(...sections.map((s) => s.label.length)) + 1;
|
|
2985
|
-
return /* @__PURE__ */
|
|
2986
|
-
/* @__PURE__ */
|
|
3353
|
+
return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
|
|
3354
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, children: [
|
|
2987
3355
|
"\u25B8 ",
|
|
2988
3356
|
selectedTemplate.name
|
|
2989
3357
|
] }),
|
|
2990
|
-
/* @__PURE__ */
|
|
2991
|
-
/* @__PURE__ */
|
|
2992
|
-
/* @__PURE__ */
|
|
2993
|
-
/* @__PURE__ */
|
|
3358
|
+
/* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: sections.map((section, idx) => /* @__PURE__ */ jsxs9(Box7, { children: [
|
|
3359
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: section.label.padEnd(labelWidth) }),
|
|
3360
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
3361
|
+
/* @__PURE__ */ jsx9(Text9, { children: section.value })
|
|
2994
3362
|
] }, idx)) }),
|
|
2995
|
-
t.steps.length > 0 && /* @__PURE__ */
|
|
2996
|
-
/* @__PURE__ */
|
|
2997
|
-
/* @__PURE__ */
|
|
3363
|
+
t.steps.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
3364
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " Provisioning Steps" }),
|
|
3365
|
+
/* @__PURE__ */ jsx9(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx9(
|
|
2998
3366
|
Table,
|
|
2999
3367
|
{
|
|
3000
3368
|
headers: ["#", "Step", "Description"],
|
|
@@ -3006,7 +3374,7 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
3006
3374
|
}
|
|
3007
3375
|
) })
|
|
3008
3376
|
] }),
|
|
3009
|
-
/* @__PURE__ */
|
|
3377
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(
|
|
3010
3378
|
SelectInput,
|
|
3011
3379
|
{
|
|
3012
3380
|
items: actionItems,
|
|
@@ -3014,9 +3382,9 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
3014
3382
|
itemComponent: MenuItem
|
|
3015
3383
|
}
|
|
3016
3384
|
) }),
|
|
3017
|
-
/* @__PURE__ */
|
|
3385
|
+
/* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
3018
3386
|
"Press ",
|
|
3019
|
-
/* @__PURE__ */
|
|
3387
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
|
|
3020
3388
|
" to go back"
|
|
3021
3389
|
] }) })
|
|
3022
3390
|
] });
|
|
@@ -3085,26 +3453,27 @@ function registerListCommand(program2) {
|
|
|
3085
3453
|
}
|
|
3086
3454
|
const deleteIndex = opts.delete ? parseInt(opts.delete, 10) : void 0;
|
|
3087
3455
|
if (deleteIndex !== void 0 && isNaN(deleteIndex)) {
|
|
3088
|
-
const { waitUntilExit: waitUntilExit2 } =
|
|
3089
|
-
/* @__PURE__ */
|
|
3456
|
+
const { waitUntilExit: waitUntilExit2 } = render6(
|
|
3457
|
+
/* @__PURE__ */ jsx9(ListView, { type, deleteIndex: void 0 })
|
|
3090
3458
|
);
|
|
3091
3459
|
await waitUntilExit2();
|
|
3092
3460
|
return;
|
|
3093
3461
|
}
|
|
3094
|
-
const { waitUntilExit } =
|
|
3095
|
-
/* @__PURE__ */
|
|
3462
|
+
const { waitUntilExit } = render6(
|
|
3463
|
+
/* @__PURE__ */ jsx9(ListView, { type, deleteIndex })
|
|
3096
3464
|
);
|
|
3097
3465
|
await waitUntilExit();
|
|
3098
3466
|
});
|
|
3099
3467
|
}
|
|
3100
3468
|
|
|
3101
3469
|
// src/commands/Migrate.tsx
|
|
3102
|
-
import { Box as
|
|
3103
|
-
import { render as
|
|
3104
|
-
import { useEffect as
|
|
3470
|
+
import { Box as Box8, Text as Text10, useApp as useApp6 } from "ink";
|
|
3471
|
+
import { render as render7 } from "ink";
|
|
3472
|
+
import { useEffect as useEffect6, useState as useState7 } from "react";
|
|
3105
3473
|
|
|
3106
3474
|
// src/core/migrate.ts
|
|
3107
3475
|
import { copyFile as copyFile2, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
3476
|
+
import { filter as filter3, map as map6 } from "@winglet/common-utils";
|
|
3108
3477
|
import YAML4 from "yaml";
|
|
3109
3478
|
init_assets();
|
|
3110
3479
|
function extractSchemaPaths(schema, prefix = []) {
|
|
@@ -3154,22 +3523,23 @@ function diffConfigFields(userData) {
|
|
|
3154
3523
|
const templateData = YAML4.parse(readAsset("config.default.yml"));
|
|
3155
3524
|
const templatePaths = extractDataPaths(templateData);
|
|
3156
3525
|
const userPaths = extractDataPaths(userData);
|
|
3157
|
-
const schemaKeys = new Set(schemaPaths
|
|
3158
|
-
const userKeys = new Set(userPaths
|
|
3526
|
+
const schemaKeys = new Set(map6(schemaPaths, pathKey));
|
|
3527
|
+
const userKeys = new Set(map6(userPaths, pathKey));
|
|
3159
3528
|
const isEditorDirective = (p) => p.length === 1 && p[0] === "yaml-language-server";
|
|
3160
3529
|
return {
|
|
3161
3530
|
// Fields present in template with defaults AND valid in schema, but missing from user
|
|
3162
|
-
added: templatePaths
|
|
3531
|
+
added: filter3(templatePaths, (p) => {
|
|
3163
3532
|
if (isEditorDirective(p)) return false;
|
|
3164
3533
|
const key = pathKey(p);
|
|
3165
3534
|
return schemaKeys.has(key) && !userKeys.has(key);
|
|
3166
3535
|
}),
|
|
3167
3536
|
// Fields in user but not in schema (truly deprecated)
|
|
3168
|
-
removed:
|
|
3537
|
+
removed: filter3(
|
|
3538
|
+
userPaths,
|
|
3169
3539
|
(p) => !isEditorDirective(p) && !schemaKeys.has(pathKey(p))
|
|
3170
3540
|
),
|
|
3171
3541
|
// Fields in user AND in schema (preserve user values)
|
|
3172
|
-
existing: userPaths
|
|
3542
|
+
existing: filter3(userPaths, (p) => schemaKeys.has(pathKey(p)))
|
|
3173
3543
|
};
|
|
3174
3544
|
}
|
|
3175
3545
|
function buildMigratedDocument(templateText, userData, diff) {
|
|
@@ -3216,15 +3586,15 @@ Run "syncpoint init" first.`
|
|
|
3216
3586
|
return {
|
|
3217
3587
|
added: [],
|
|
3218
3588
|
deprecated: [],
|
|
3219
|
-
preserved: diff.existing
|
|
3589
|
+
preserved: map6(diff.existing, pathKey),
|
|
3220
3590
|
backupPath: "",
|
|
3221
3591
|
migrated: false
|
|
3222
3592
|
};
|
|
3223
3593
|
}
|
|
3224
3594
|
const result = {
|
|
3225
|
-
added: diff.added
|
|
3226
|
-
deprecated: diff.removed
|
|
3227
|
-
preserved: diff.existing
|
|
3595
|
+
added: map6(diff.added, pathKey),
|
|
3596
|
+
deprecated: map6(diff.removed, pathKey),
|
|
3597
|
+
preserved: map6(diff.existing, pathKey),
|
|
3228
3598
|
backupPath: "",
|
|
3229
3599
|
migrated: false
|
|
3230
3600
|
};
|
|
@@ -3249,13 +3619,13 @@ ${(validation.errors ?? []).join("\n")}`
|
|
|
3249
3619
|
}
|
|
3250
3620
|
|
|
3251
3621
|
// src/commands/Migrate.tsx
|
|
3252
|
-
import { jsx as
|
|
3622
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3253
3623
|
var MigrateView = ({ dryRun }) => {
|
|
3254
|
-
const { exit } =
|
|
3255
|
-
const [result, setResult] =
|
|
3256
|
-
const [error, setError] =
|
|
3257
|
-
const [loading, setLoading] =
|
|
3258
|
-
|
|
3624
|
+
const { exit } = useApp6();
|
|
3625
|
+
const [result, setResult] = useState7(null);
|
|
3626
|
+
const [error, setError] = useState7(null);
|
|
3627
|
+
const [loading, setLoading] = useState7(true);
|
|
3628
|
+
useEffect6(() => {
|
|
3259
3629
|
(async () => {
|
|
3260
3630
|
try {
|
|
3261
3631
|
const res = await migrateConfig({ dryRun });
|
|
@@ -3270,54 +3640,54 @@ var MigrateView = ({ dryRun }) => {
|
|
|
3270
3640
|
})();
|
|
3271
3641
|
}, []);
|
|
3272
3642
|
if (error) {
|
|
3273
|
-
return /* @__PURE__ */
|
|
3643
|
+
return /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
|
|
3274
3644
|
"\u2717 ",
|
|
3275
3645
|
error
|
|
3276
3646
|
] }) });
|
|
3277
3647
|
}
|
|
3278
3648
|
if (loading) {
|
|
3279
|
-
return /* @__PURE__ */
|
|
3649
|
+
return /* @__PURE__ */ jsx10(Text10, { children: "Analyzing config..." });
|
|
3280
3650
|
}
|
|
3281
3651
|
if (!result) return null;
|
|
3282
3652
|
if (!result.migrated && result.added.length === 0 && result.deprecated.length === 0) {
|
|
3283
|
-
return /* @__PURE__ */
|
|
3653
|
+
return /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713 Config is already up to date." }) });
|
|
3284
3654
|
}
|
|
3285
|
-
return /* @__PURE__ */
|
|
3286
|
-
dryRun && /* @__PURE__ */
|
|
3287
|
-
result.added.length > 0 && /* @__PURE__ */
|
|
3288
|
-
/* @__PURE__ */
|
|
3289
|
-
result.added.map((field, i) => /* @__PURE__ */
|
|
3655
|
+
return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
|
|
3656
|
+
dryRun && /* @__PURE__ */ jsx10(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "yellow", bold: true, children: "[dry-run] Preview only \u2014 no changes written." }) }),
|
|
3657
|
+
result.added.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
|
|
3658
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: "New fields (added with defaults):" }),
|
|
3659
|
+
result.added.map((field, i) => /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
3290
3660
|
" ",
|
|
3291
|
-
/* @__PURE__ */
|
|
3661
|
+
/* @__PURE__ */ jsx10(Text10, { color: "green", children: "+" }),
|
|
3292
3662
|
" ",
|
|
3293
3663
|
field
|
|
3294
3664
|
] }, i))
|
|
3295
3665
|
] }),
|
|
3296
|
-
result.deprecated.length > 0 && /* @__PURE__ */
|
|
3297
|
-
/* @__PURE__ */
|
|
3298
|
-
result.deprecated.map((field, i) => /* @__PURE__ */
|
|
3666
|
+
result.deprecated.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: result.added.length > 0 ? 1 : 0, children: [
|
|
3667
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: "Deprecated fields (commented out):" }),
|
|
3668
|
+
result.deprecated.map((field, i) => /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
3299
3669
|
" ",
|
|
3300
|
-
/* @__PURE__ */
|
|
3670
|
+
/* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "~" }),
|
|
3301
3671
|
" ",
|
|
3302
3672
|
field
|
|
3303
3673
|
] }, i))
|
|
3304
3674
|
] }),
|
|
3305
|
-
result.preserved.length > 0 && /* @__PURE__ */
|
|
3306
|
-
/* @__PURE__ */
|
|
3675
|
+
result.preserved.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
3676
|
+
/* @__PURE__ */ jsxs10(Text10, { bold: true, children: [
|
|
3307
3677
|
"Preserved fields (",
|
|
3308
3678
|
result.preserved.length,
|
|
3309
3679
|
"):"
|
|
3310
3680
|
] }),
|
|
3311
|
-
result.preserved.map((field, i) => /* @__PURE__ */
|
|
3681
|
+
result.preserved.map((field, i) => /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
3312
3682
|
" ",
|
|
3313
|
-
/* @__PURE__ */
|
|
3683
|
+
/* @__PURE__ */ jsx10(Text10, { color: "blue", children: "\u2022" }),
|
|
3314
3684
|
" ",
|
|
3315
3685
|
field
|
|
3316
3686
|
] }, i))
|
|
3317
3687
|
] }),
|
|
3318
|
-
result.migrated && /* @__PURE__ */
|
|
3319
|
-
/* @__PURE__ */
|
|
3320
|
-
result.backupPath && /* @__PURE__ */
|
|
3688
|
+
result.migrated && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
3689
|
+
/* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713 Migration complete." }),
|
|
3690
|
+
result.backupPath && /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
3321
3691
|
" ",
|
|
3322
3692
|
"Backup saved to: ",
|
|
3323
3693
|
result.backupPath
|
|
@@ -3339,50 +3709,50 @@ function registerMigrateCommand(program2) {
|
|
|
3339
3709
|
}
|
|
3340
3710
|
return;
|
|
3341
3711
|
}
|
|
3342
|
-
const { waitUntilExit } =
|
|
3343
|
-
/* @__PURE__ */
|
|
3712
|
+
const { waitUntilExit } = render7(
|
|
3713
|
+
/* @__PURE__ */ jsx10(MigrateView, { dryRun: opts.dryRun ?? false })
|
|
3344
3714
|
);
|
|
3345
3715
|
await waitUntilExit();
|
|
3346
3716
|
});
|
|
3347
3717
|
}
|
|
3348
3718
|
|
|
3349
3719
|
// src/commands/Provision.tsx
|
|
3350
|
-
import { Box as
|
|
3351
|
-
import { render as
|
|
3352
|
-
import { useEffect as
|
|
3720
|
+
import { Box as Box10, Text as Text12, useApp as useApp7 } from "ink";
|
|
3721
|
+
import { render as render8 } from "ink";
|
|
3722
|
+
import { useEffect as useEffect7, useState as useState8 } from "react";
|
|
3353
3723
|
|
|
3354
3724
|
// src/components/StepRunner.tsx
|
|
3355
|
-
import { Box as
|
|
3725
|
+
import { Box as Box9, Static as Static2, Text as Text11 } from "ink";
|
|
3356
3726
|
import Spinner2 from "ink-spinner";
|
|
3357
|
-
import { jsx as
|
|
3727
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3358
3728
|
var StepIcon = ({ status }) => {
|
|
3359
3729
|
switch (status) {
|
|
3360
3730
|
case "success":
|
|
3361
|
-
return /* @__PURE__ */
|
|
3731
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" });
|
|
3362
3732
|
case "running":
|
|
3363
|
-
return /* @__PURE__ */
|
|
3733
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }) });
|
|
3364
3734
|
case "skipped":
|
|
3365
|
-
return /* @__PURE__ */
|
|
3735
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "blue", children: "\u23ED" });
|
|
3366
3736
|
case "failed":
|
|
3367
|
-
return /* @__PURE__ */
|
|
3737
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "red", children: "\u2717" });
|
|
3368
3738
|
case "pending":
|
|
3369
3739
|
default:
|
|
3370
|
-
return /* @__PURE__ */
|
|
3740
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "\u25CB" });
|
|
3371
3741
|
}
|
|
3372
3742
|
};
|
|
3373
3743
|
var StepStatusText = ({ step }) => {
|
|
3374
3744
|
switch (step.status) {
|
|
3375
3745
|
case "success":
|
|
3376
|
-
return /* @__PURE__ */
|
|
3746
|
+
return /* @__PURE__ */ jsxs11(Text11, { color: "green", children: [
|
|
3377
3747
|
"Done",
|
|
3378
3748
|
step.duration != null ? ` (${Math.round(step.duration / 1e3)}s)` : ""
|
|
3379
3749
|
] });
|
|
3380
3750
|
case "running":
|
|
3381
|
-
return /* @__PURE__ */
|
|
3751
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "Running..." });
|
|
3382
3752
|
case "skipped":
|
|
3383
|
-
return /* @__PURE__ */
|
|
3753
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "blue", children: "Skipped (already installed)" });
|
|
3384
3754
|
case "failed":
|
|
3385
|
-
return /* @__PURE__ */
|
|
3755
|
+
return /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
|
|
3386
3756
|
"Failed",
|
|
3387
3757
|
step.error ? `: ${step.error}` : ""
|
|
3388
3758
|
] });
|
|
@@ -3396,13 +3766,13 @@ var StepItemView = ({
|
|
|
3396
3766
|
index,
|
|
3397
3767
|
total,
|
|
3398
3768
|
isLast
|
|
3399
|
-
}) => /* @__PURE__ */
|
|
3400
|
-
/* @__PURE__ */
|
|
3769
|
+
}) => /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: isLast ? 0 : 1, children: [
|
|
3770
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
3401
3771
|
" ",
|
|
3402
|
-
/* @__PURE__ */
|
|
3403
|
-
/* @__PURE__ */
|
|
3772
|
+
/* @__PURE__ */ jsx11(StepIcon, { status: step.status }),
|
|
3773
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
3404
3774
|
" ",
|
|
3405
|
-
/* @__PURE__ */
|
|
3775
|
+
/* @__PURE__ */ jsxs11(Text11, { bold: true, children: [
|
|
3406
3776
|
"Step ",
|
|
3407
3777
|
index + 1,
|
|
3408
3778
|
"/",
|
|
@@ -3412,13 +3782,13 @@ var StepItemView = ({
|
|
|
3412
3782
|
step.name
|
|
3413
3783
|
] })
|
|
3414
3784
|
] }),
|
|
3415
|
-
step.output && step.status !== "pending" && /* @__PURE__ */
|
|
3785
|
+
step.output && step.status !== "pending" && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
|
|
3416
3786
|
" ",
|
|
3417
3787
|
step.output
|
|
3418
3788
|
] }),
|
|
3419
|
-
/* @__PURE__ */
|
|
3789
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
3420
3790
|
" ",
|
|
3421
|
-
/* @__PURE__ */
|
|
3791
|
+
/* @__PURE__ */ jsx11(StepStatusText, { step })
|
|
3422
3792
|
] })
|
|
3423
3793
|
] });
|
|
3424
3794
|
var StepRunner = ({ steps, total }) => {
|
|
@@ -3433,8 +3803,8 @@ var StepRunner = ({ steps, total }) => {
|
|
|
3433
3803
|
}
|
|
3434
3804
|
});
|
|
3435
3805
|
const lastIdx = steps.length - 1;
|
|
3436
|
-
return /* @__PURE__ */
|
|
3437
|
-
/* @__PURE__ */
|
|
3806
|
+
return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
|
|
3807
|
+
/* @__PURE__ */ jsx11(Static2, { items: completedSteps, children: (item) => /* @__PURE__ */ jsx11(
|
|
3438
3808
|
StepItemView,
|
|
3439
3809
|
{
|
|
3440
3810
|
step: item,
|
|
@@ -3444,7 +3814,7 @@ var StepRunner = ({ steps, total }) => {
|
|
|
3444
3814
|
},
|
|
3445
3815
|
item.idx
|
|
3446
3816
|
) }),
|
|
3447
|
-
activeSteps.map((item) => /* @__PURE__ */
|
|
3817
|
+
activeSteps.map((item) => /* @__PURE__ */ jsx11(
|
|
3448
3818
|
StepItemView,
|
|
3449
3819
|
{
|
|
3450
3820
|
step: item,
|
|
@@ -3490,26 +3860,26 @@ ${pc2.red("\u2717")} Sudo authentication failed or was cancelled. Aborting.`
|
|
|
3490
3860
|
}
|
|
3491
3861
|
|
|
3492
3862
|
// src/commands/Provision.tsx
|
|
3493
|
-
import { jsx as
|
|
3863
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3494
3864
|
var ProvisionView = ({
|
|
3495
3865
|
template,
|
|
3496
3866
|
templatePath,
|
|
3497
3867
|
options
|
|
3498
3868
|
}) => {
|
|
3499
|
-
const { exit } =
|
|
3500
|
-
const [phase, setPhase] =
|
|
3869
|
+
const { exit } = useApp7();
|
|
3870
|
+
const [phase, setPhase] = useState8(
|
|
3501
3871
|
options.dryRun ? "done" : "running"
|
|
3502
3872
|
);
|
|
3503
|
-
const [steps, setSteps] =
|
|
3873
|
+
const [steps, setSteps] = useState8(
|
|
3504
3874
|
template.steps.map((s) => ({
|
|
3505
3875
|
name: s.name,
|
|
3506
3876
|
status: "pending",
|
|
3507
3877
|
output: s.description
|
|
3508
3878
|
}))
|
|
3509
3879
|
);
|
|
3510
|
-
const [currentStep, setCurrentStep] =
|
|
3511
|
-
const [error, setError] =
|
|
3512
|
-
|
|
3880
|
+
const [currentStep, setCurrentStep] = useState8(0);
|
|
3881
|
+
const [error, setError] = useState8(null);
|
|
3882
|
+
useEffect7(() => {
|
|
3513
3883
|
if (options.dryRun) {
|
|
3514
3884
|
setTimeout(() => exit(), 100);
|
|
3515
3885
|
return;
|
|
@@ -3551,7 +3921,7 @@ var ProvisionView = ({
|
|
|
3551
3921
|
})();
|
|
3552
3922
|
}, []);
|
|
3553
3923
|
if (phase === "error" || error) {
|
|
3554
|
-
return /* @__PURE__ */
|
|
3924
|
+
return /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
|
|
3555
3925
|
"\u2717 ",
|
|
3556
3926
|
error
|
|
3557
3927
|
] }) });
|
|
@@ -3559,27 +3929,27 @@ var ProvisionView = ({
|
|
|
3559
3929
|
const successCount = steps.filter((s) => s.status === "success").length;
|
|
3560
3930
|
const skippedCount = steps.filter((s) => s.status === "skipped").length;
|
|
3561
3931
|
const failedCount = steps.filter((s) => s.status === "failed").length;
|
|
3562
|
-
return /* @__PURE__ */
|
|
3563
|
-
/* @__PURE__ */
|
|
3564
|
-
/* @__PURE__ */
|
|
3932
|
+
return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
|
|
3933
|
+
/* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
|
|
3934
|
+
/* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
|
|
3565
3935
|
"\u25B8 ",
|
|
3566
3936
|
template.name
|
|
3567
3937
|
] }),
|
|
3568
|
-
template.description && /* @__PURE__ */
|
|
3938
|
+
template.description && /* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
|
|
3569
3939
|
" ",
|
|
3570
3940
|
template.description
|
|
3571
3941
|
] })
|
|
3572
3942
|
] }),
|
|
3573
|
-
options.dryRun && phase === "done" && /* @__PURE__ */
|
|
3574
|
-
/* @__PURE__ */
|
|
3575
|
-
template.sudo && /* @__PURE__ */
|
|
3943
|
+
options.dryRun && phase === "done" && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
|
|
3944
|
+
/* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "(dry-run) Showing execution plan only" }),
|
|
3945
|
+
template.sudo && /* @__PURE__ */ jsxs12(Text12, { color: "yellow", children: [
|
|
3576
3946
|
" ",
|
|
3577
3947
|
"\u26A0 This template requires sudo privileges (will prompt on actual run)"
|
|
3578
3948
|
] }),
|
|
3579
|
-
/* @__PURE__ */
|
|
3580
|
-
/* @__PURE__ */
|
|
3949
|
+
/* @__PURE__ */ jsx12(Box10, { flexDirection: "column", marginTop: 1, children: template.steps.map((step, idx) => /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
|
|
3950
|
+
/* @__PURE__ */ jsxs12(Text12, { children: [
|
|
3581
3951
|
" ",
|
|
3582
|
-
/* @__PURE__ */
|
|
3952
|
+
/* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
|
|
3583
3953
|
"Step ",
|
|
3584
3954
|
idx + 1,
|
|
3585
3955
|
"/",
|
|
@@ -3588,18 +3958,18 @@ var ProvisionView = ({
|
|
|
3588
3958
|
" ",
|
|
3589
3959
|
step.name
|
|
3590
3960
|
] }),
|
|
3591
|
-
step.description && /* @__PURE__ */
|
|
3961
|
+
step.description && /* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
|
|
3592
3962
|
" ",
|
|
3593
3963
|
step.description
|
|
3594
3964
|
] }),
|
|
3595
|
-
step.skip_if && /* @__PURE__ */
|
|
3965
|
+
step.skip_if && /* @__PURE__ */ jsxs12(Text12, { color: "blue", children: [
|
|
3596
3966
|
" ",
|
|
3597
3967
|
"Skip condition: ",
|
|
3598
3968
|
step.skip_if
|
|
3599
3969
|
] })
|
|
3600
3970
|
] }, idx)) })
|
|
3601
3971
|
] }),
|
|
3602
|
-
(phase === "running" || phase === "done" && !options.dryRun) && /* @__PURE__ */
|
|
3972
|
+
(phase === "running" || phase === "done" && !options.dryRun) && /* @__PURE__ */ jsx12(
|
|
3603
3973
|
StepRunner,
|
|
3604
3974
|
{
|
|
3605
3975
|
steps,
|
|
@@ -3607,34 +3977,34 @@ var ProvisionView = ({
|
|
|
3607
3977
|
total: template.steps.length
|
|
3608
3978
|
}
|
|
3609
3979
|
),
|
|
3610
|
-
phase === "done" && !options.dryRun && /* @__PURE__ */
|
|
3611
|
-
/* @__PURE__ */
|
|
3980
|
+
phase === "done" && !options.dryRun && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
|
|
3981
|
+
/* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
|
|
3612
3982
|
" ",
|
|
3613
3983
|
"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
3614
3984
|
] }),
|
|
3615
|
-
/* @__PURE__ */
|
|
3985
|
+
/* @__PURE__ */ jsxs12(Text12, { children: [
|
|
3616
3986
|
" ",
|
|
3617
3987
|
"Result: ",
|
|
3618
|
-
/* @__PURE__ */
|
|
3988
|
+
/* @__PURE__ */ jsxs12(Text12, { color: "green", children: [
|
|
3619
3989
|
successCount,
|
|
3620
3990
|
" succeeded"
|
|
3621
3991
|
] }),
|
|
3622
3992
|
" \xB7",
|
|
3623
3993
|
" ",
|
|
3624
|
-
/* @__PURE__ */
|
|
3994
|
+
/* @__PURE__ */ jsxs12(Text12, { color: "blue", children: [
|
|
3625
3995
|
skippedCount,
|
|
3626
3996
|
" skipped"
|
|
3627
3997
|
] }),
|
|
3628
3998
|
" \xB7",
|
|
3629
3999
|
" ",
|
|
3630
|
-
/* @__PURE__ */
|
|
4000
|
+
/* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
|
|
3631
4001
|
failedCount,
|
|
3632
4002
|
" failed"
|
|
3633
4003
|
] })
|
|
3634
4004
|
] }),
|
|
3635
|
-
template.backup && !options.skipRestore && /* @__PURE__ */
|
|
3636
|
-
/* @__PURE__ */
|
|
3637
|
-
/* @__PURE__ */
|
|
4005
|
+
template.backup && !options.skipRestore && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
|
|
4006
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Proceeding with config file restore..." }),
|
|
4007
|
+
/* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
|
|
3638
4008
|
" ",
|
|
3639
4009
|
"Backup link: ",
|
|
3640
4010
|
template.backup
|
|
@@ -3727,8 +4097,8 @@ function registerProvisionCommand(program2) {
|
|
|
3727
4097
|
}
|
|
3728
4098
|
return;
|
|
3729
4099
|
}
|
|
3730
|
-
const { waitUntilExit } =
|
|
3731
|
-
/* @__PURE__ */
|
|
4100
|
+
const { waitUntilExit } = render8(
|
|
4101
|
+
/* @__PURE__ */ jsx12(
|
|
3732
4102
|
ProvisionView,
|
|
3733
4103
|
{
|
|
3734
4104
|
template: tmpl,
|
|
@@ -3746,21 +4116,21 @@ function registerProvisionCommand(program2) {
|
|
|
3746
4116
|
}
|
|
3747
4117
|
|
|
3748
4118
|
// src/commands/Restore.tsx
|
|
3749
|
-
import { Box as
|
|
3750
|
-
import { render as
|
|
4119
|
+
import { Box as Box11, Text as Text13, useApp as useApp8 } from "ink";
|
|
4120
|
+
import { render as render9 } from "ink";
|
|
3751
4121
|
import SelectInput2 from "ink-select-input";
|
|
3752
|
-
import { useEffect as
|
|
3753
|
-
import { jsx as
|
|
4122
|
+
import { useEffect as useEffect8, useState as useState9 } from "react";
|
|
4123
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3754
4124
|
var RestoreView = ({ filename, options }) => {
|
|
3755
|
-
const { exit } =
|
|
3756
|
-
const [phase, setPhase] =
|
|
3757
|
-
const [backups, setBackups] =
|
|
3758
|
-
const [selectedPath, setSelectedPath] =
|
|
3759
|
-
const [plan, setPlan] =
|
|
3760
|
-
const [result, setResult] =
|
|
3761
|
-
const [safetyDone, setSafetyDone] =
|
|
3762
|
-
const [error, setError] =
|
|
3763
|
-
|
|
4125
|
+
const { exit } = useApp8();
|
|
4126
|
+
const [phase, setPhase] = useState9("loading");
|
|
4127
|
+
const [backups, setBackups] = useState9([]);
|
|
4128
|
+
const [selectedPath, setSelectedPath] = useState9(null);
|
|
4129
|
+
const [plan, setPlan] = useState9(null);
|
|
4130
|
+
const [result, setResult] = useState9(null);
|
|
4131
|
+
const [safetyDone, setSafetyDone] = useState9(false);
|
|
4132
|
+
const [error, setError] = useState9(null);
|
|
4133
|
+
useEffect8(() => {
|
|
3764
4134
|
(async () => {
|
|
3765
4135
|
try {
|
|
3766
4136
|
const config = await loadConfig();
|
|
@@ -3794,7 +4164,7 @@ var RestoreView = ({ filename, options }) => {
|
|
|
3794
4164
|
}
|
|
3795
4165
|
})();
|
|
3796
4166
|
}, []);
|
|
3797
|
-
|
|
4167
|
+
useEffect8(() => {
|
|
3798
4168
|
if (phase !== "planning" || !selectedPath) return;
|
|
3799
4169
|
(async () => {
|
|
3800
4170
|
try {
|
|
@@ -3842,7 +4212,7 @@ var RestoreView = ({ filename, options }) => {
|
|
|
3842
4212
|
}
|
|
3843
4213
|
};
|
|
3844
4214
|
if (phase === "error" || error) {
|
|
3845
|
-
return /* @__PURE__ */
|
|
4215
|
+
return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
|
|
3846
4216
|
"\u2717 ",
|
|
3847
4217
|
error
|
|
3848
4218
|
] }) });
|
|
@@ -3853,30 +4223,30 @@ var RestoreView = ({ filename, options }) => {
|
|
|
3853
4223
|
}));
|
|
3854
4224
|
const currentHostname = getHostname();
|
|
3855
4225
|
const isRemoteBackup = plan?.metadata.hostname && plan.metadata.hostname !== currentHostname;
|
|
3856
|
-
return /* @__PURE__ */
|
|
3857
|
-
phase === "loading" && /* @__PURE__ */
|
|
3858
|
-
phase === "selecting" && /* @__PURE__ */
|
|
3859
|
-
/* @__PURE__ */
|
|
3860
|
-
/* @__PURE__ */
|
|
4226
|
+
return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
|
|
4227
|
+
phase === "loading" && /* @__PURE__ */ jsx13(Text13, { children: "\u25B8 Loading backup list..." }),
|
|
4228
|
+
phase === "selecting" && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
|
|
4229
|
+
/* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Select backup" }),
|
|
4230
|
+
/* @__PURE__ */ jsx13(SelectInput2, { items: selectItems, onSelect: handleSelect })
|
|
3861
4231
|
] }),
|
|
3862
|
-
(phase === "planning" || phase === "confirming" || phase === "restoring" || phase === "done" && plan) && plan && /* @__PURE__ */
|
|
3863
|
-
/* @__PURE__ */
|
|
3864
|
-
/* @__PURE__ */
|
|
4232
|
+
(phase === "planning" || phase === "confirming" || phase === "restoring" || phase === "done" && plan) && plan && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
|
|
4233
|
+
/* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginBottom: 1, children: [
|
|
4234
|
+
/* @__PURE__ */ jsxs13(Text13, { bold: true, children: [
|
|
3865
4235
|
"\u25B8 Metadata (",
|
|
3866
4236
|
plan.metadata.config.filename ?? "",
|
|
3867
4237
|
")"
|
|
3868
4238
|
] }),
|
|
3869
|
-
/* @__PURE__ */
|
|
4239
|
+
/* @__PURE__ */ jsxs13(Text13, { children: [
|
|
3870
4240
|
" ",
|
|
3871
4241
|
"Host: ",
|
|
3872
4242
|
plan.metadata.hostname
|
|
3873
4243
|
] }),
|
|
3874
|
-
/* @__PURE__ */
|
|
4244
|
+
/* @__PURE__ */ jsxs13(Text13, { children: [
|
|
3875
4245
|
" ",
|
|
3876
4246
|
"Created: ",
|
|
3877
4247
|
plan.metadata.createdAt
|
|
3878
4248
|
] }),
|
|
3879
|
-
/* @__PURE__ */
|
|
4249
|
+
/* @__PURE__ */ jsxs13(Text13, { children: [
|
|
3880
4250
|
" ",
|
|
3881
4251
|
"Files: ",
|
|
3882
4252
|
plan.metadata.summary.fileCount,
|
|
@@ -3884,15 +4254,15 @@ var RestoreView = ({ filename, options }) => {
|
|
|
3884
4254
|
formatBytes(plan.metadata.summary.totalSize),
|
|
3885
4255
|
")"
|
|
3886
4256
|
] }),
|
|
3887
|
-
isRemoteBackup && /* @__PURE__ */
|
|
4257
|
+
isRemoteBackup && /* @__PURE__ */ jsxs13(Text13, { color: "yellow", children: [
|
|
3888
4258
|
" ",
|
|
3889
4259
|
"\u26A0 This backup was created on a different machine (",
|
|
3890
4260
|
plan.metadata.hostname,
|
|
3891
4261
|
")"
|
|
3892
4262
|
] })
|
|
3893
4263
|
] }),
|
|
3894
|
-
/* @__PURE__ */
|
|
3895
|
-
/* @__PURE__ */
|
|
4264
|
+
/* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginBottom: 1, children: [
|
|
4265
|
+
/* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Restore plan:" }),
|
|
3896
4266
|
plan.actions.map((action, idx) => {
|
|
3897
4267
|
let icon;
|
|
3898
4268
|
let color;
|
|
@@ -3914,45 +4284,45 @@ var RestoreView = ({ filename, options }) => {
|
|
|
3914
4284
|
label = "(not present)";
|
|
3915
4285
|
break;
|
|
3916
4286
|
}
|
|
3917
|
-
return /* @__PURE__ */
|
|
4287
|
+
return /* @__PURE__ */ jsxs13(Text13, { children: [
|
|
3918
4288
|
" ",
|
|
3919
|
-
/* @__PURE__ */
|
|
4289
|
+
/* @__PURE__ */ jsx13(Text13, { color, children: icon.padEnd(8) }),
|
|
3920
4290
|
" ",
|
|
3921
4291
|
contractTilde(action.path),
|
|
3922
4292
|
" ",
|
|
3923
|
-
/* @__PURE__ */
|
|
4293
|
+
/* @__PURE__ */ jsx13(Text13, { color: "gray", children: label })
|
|
3924
4294
|
] }, idx);
|
|
3925
4295
|
})
|
|
3926
4296
|
] }),
|
|
3927
|
-
options.dryRun && phase === "done" && /* @__PURE__ */
|
|
4297
|
+
options.dryRun && phase === "done" && /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: "(dry-run) No actual restore was performed" })
|
|
3928
4298
|
] }),
|
|
3929
|
-
phase === "confirming" && /* @__PURE__ */
|
|
3930
|
-
phase === "restoring" && /* @__PURE__ */
|
|
3931
|
-
safetyDone && /* @__PURE__ */
|
|
3932
|
-
/* @__PURE__ */
|
|
4299
|
+
phase === "confirming" && /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsx13(Confirm, { message: "Proceed with restore?", onConfirm: handleConfirm }) }),
|
|
4300
|
+
phase === "restoring" && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
|
|
4301
|
+
safetyDone && /* @__PURE__ */ jsxs13(Text13, { children: [
|
|
4302
|
+
/* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
|
|
3933
4303
|
" Safety backup of current files complete"
|
|
3934
4304
|
] }),
|
|
3935
|
-
/* @__PURE__ */
|
|
4305
|
+
/* @__PURE__ */ jsx13(Text13, { children: "\u25B8 Restoring..." })
|
|
3936
4306
|
] }),
|
|
3937
|
-
phase === "done" && result && !options.dryRun && /* @__PURE__ */
|
|
3938
|
-
safetyDone && /* @__PURE__ */
|
|
3939
|
-
/* @__PURE__ */
|
|
4307
|
+
phase === "done" && result && !options.dryRun && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
4308
|
+
safetyDone && /* @__PURE__ */ jsxs13(Text13, { children: [
|
|
4309
|
+
/* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
|
|
3940
4310
|
" Safety backup of current files complete"
|
|
3941
4311
|
] }),
|
|
3942
|
-
/* @__PURE__ */
|
|
3943
|
-
/* @__PURE__ */
|
|
4312
|
+
/* @__PURE__ */ jsx13(Text13, { color: "green", bold: true, children: "\u2713 Restore complete" }),
|
|
4313
|
+
/* @__PURE__ */ jsxs13(Text13, { children: [
|
|
3944
4314
|
" ",
|
|
3945
4315
|
"Restored: ",
|
|
3946
4316
|
result.restoredFiles.length,
|
|
3947
4317
|
" files"
|
|
3948
4318
|
] }),
|
|
3949
|
-
/* @__PURE__ */
|
|
4319
|
+
/* @__PURE__ */ jsxs13(Text13, { children: [
|
|
3950
4320
|
" ",
|
|
3951
4321
|
"Skipped: ",
|
|
3952
4322
|
result.skippedFiles.length,
|
|
3953
4323
|
" files"
|
|
3954
4324
|
] }),
|
|
3955
|
-
result.safetyBackupPath && /* @__PURE__ */
|
|
4325
|
+
result.safetyBackupPath && /* @__PURE__ */ jsxs13(Text13, { children: [
|
|
3956
4326
|
" ",
|
|
3957
4327
|
"Safety backup: ",
|
|
3958
4328
|
contractTilde(result.safetyBackupPath)
|
|
@@ -4008,8 +4378,8 @@ function registerRestoreCommand(program2) {
|
|
|
4008
4378
|
}
|
|
4009
4379
|
return;
|
|
4010
4380
|
}
|
|
4011
|
-
const { waitUntilExit } =
|
|
4012
|
-
/* @__PURE__ */
|
|
4381
|
+
const { waitUntilExit } = render9(
|
|
4382
|
+
/* @__PURE__ */ jsx13(RestoreView, { filename, options: { dryRun: opts.dryRun } })
|
|
4013
4383
|
);
|
|
4014
4384
|
await waitUntilExit();
|
|
4015
4385
|
});
|
|
@@ -4017,12 +4387,12 @@ function registerRestoreCommand(program2) {
|
|
|
4017
4387
|
|
|
4018
4388
|
// src/commands/Status.tsx
|
|
4019
4389
|
import { readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
|
|
4020
|
-
import { join as
|
|
4021
|
-
import { Box as
|
|
4022
|
-
import { render as
|
|
4390
|
+
import { join as join13 } from "path";
|
|
4391
|
+
import { Box as Box12, Text as Text14, useApp as useApp9, useInput as useInput3 } from "ink";
|
|
4392
|
+
import { render as render10 } from "ink";
|
|
4023
4393
|
import SelectInput3 from "ink-select-input";
|
|
4024
|
-
import { useEffect as
|
|
4025
|
-
import { jsx as
|
|
4394
|
+
import { useEffect as useEffect9, useState as useState10 } from "react";
|
|
4395
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4026
4396
|
function getDirStats(dirPath) {
|
|
4027
4397
|
try {
|
|
4028
4398
|
const entries = readdirSync(dirPath);
|
|
@@ -4030,9 +4400,9 @@ function getDirStats(dirPath) {
|
|
|
4030
4400
|
let count = 0;
|
|
4031
4401
|
for (const entry of entries) {
|
|
4032
4402
|
try {
|
|
4033
|
-
const
|
|
4034
|
-
if (
|
|
4035
|
-
totalSize +=
|
|
4403
|
+
const stat5 = statSync(join13(dirPath, entry));
|
|
4404
|
+
if (stat5.isFile()) {
|
|
4405
|
+
totalSize += stat5.size;
|
|
4036
4406
|
count++;
|
|
4037
4407
|
}
|
|
4038
4408
|
} catch {
|
|
@@ -4044,34 +4414,34 @@ function getDirStats(dirPath) {
|
|
|
4044
4414
|
}
|
|
4045
4415
|
}
|
|
4046
4416
|
var DisplayActionItem = ({ isSelected = false, label }) => {
|
|
4047
|
-
return /* @__PURE__ */
|
|
4417
|
+
return /* @__PURE__ */ jsx14(Text14, { bold: isSelected, children: label });
|
|
4048
4418
|
};
|
|
4049
4419
|
var CleanupActionItem = ({ isSelected = false, label }) => {
|
|
4050
4420
|
if (label === "Cancel" || label === "Select specific backups to delete") {
|
|
4051
|
-
return /* @__PURE__ */
|
|
4421
|
+
return /* @__PURE__ */ jsx14(Text14, { bold: isSelected, children: label });
|
|
4052
4422
|
}
|
|
4053
4423
|
const parts = label.split(/\s{2,}/);
|
|
4054
4424
|
if (parts.length === 2) {
|
|
4055
|
-
return /* @__PURE__ */
|
|
4425
|
+
return /* @__PURE__ */ jsxs14(Text14, { bold: isSelected, children: [
|
|
4056
4426
|
parts[0],
|
|
4057
4427
|
" ",
|
|
4058
|
-
/* @__PURE__ */
|
|
4428
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: parts[1] })
|
|
4059
4429
|
] });
|
|
4060
4430
|
}
|
|
4061
|
-
return /* @__PURE__ */
|
|
4431
|
+
return /* @__PURE__ */ jsx14(Text14, { bold: isSelected, children: label });
|
|
4062
4432
|
};
|
|
4063
4433
|
var StatusView = ({ cleanup }) => {
|
|
4064
|
-
const { exit } =
|
|
4065
|
-
const [phase, setPhase] =
|
|
4066
|
-
const [status, setStatus] =
|
|
4067
|
-
const [backups, setBackups] =
|
|
4068
|
-
const [cleanupAction, setCleanupAction] =
|
|
4069
|
-
const [cleanupMessage, setCleanupMessage] =
|
|
4070
|
-
const [error, setError] =
|
|
4071
|
-
const [selectedForDeletion, setSelectedForDeletion] =
|
|
4434
|
+
const { exit } = useApp9();
|
|
4435
|
+
const [phase, setPhase] = useState10("loading");
|
|
4436
|
+
const [status, setStatus] = useState10(null);
|
|
4437
|
+
const [backups, setBackups] = useState10([]);
|
|
4438
|
+
const [cleanupAction, setCleanupAction] = useState10(null);
|
|
4439
|
+
const [cleanupMessage, setCleanupMessage] = useState10("");
|
|
4440
|
+
const [error, setError] = useState10(null);
|
|
4441
|
+
const [selectedForDeletion, setSelectedForDeletion] = useState10(
|
|
4072
4442
|
[]
|
|
4073
4443
|
);
|
|
4074
|
-
const [backupDir, setBackupDir] =
|
|
4444
|
+
const [backupDir, setBackupDir] = useState10(getSubDir("backups"));
|
|
4075
4445
|
useInput3((_input, key) => {
|
|
4076
4446
|
if (!key.escape) return;
|
|
4077
4447
|
if (phase === "display") {
|
|
@@ -4083,7 +4453,7 @@ var StatusView = ({ cleanup }) => {
|
|
|
4083
4453
|
setPhase("cleanup");
|
|
4084
4454
|
}
|
|
4085
4455
|
});
|
|
4086
|
-
|
|
4456
|
+
useEffect9(() => {
|
|
4087
4457
|
(async () => {
|
|
4088
4458
|
try {
|
|
4089
4459
|
const config = await loadConfig();
|
|
@@ -4182,7 +4552,7 @@ var StatusView = ({ cleanup }) => {
|
|
|
4182
4552
|
try {
|
|
4183
4553
|
const entries = readdirSync(logsDir);
|
|
4184
4554
|
for (const entry of entries) {
|
|
4185
|
-
const logPath =
|
|
4555
|
+
const logPath = join13(logsDir, entry);
|
|
4186
4556
|
if (!isInsideDir(logPath, logsDir))
|
|
4187
4557
|
throw new Error(
|
|
4188
4558
|
`Refusing to delete file outside logs directory: ${logPath}`
|
|
@@ -4232,26 +4602,26 @@ var StatusView = ({ cleanup }) => {
|
|
|
4232
4602
|
}
|
|
4233
4603
|
};
|
|
4234
4604
|
if (phase === "error" || error) {
|
|
4235
|
-
return /* @__PURE__ */
|
|
4605
|
+
return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs14(Text14, { color: "red", children: [
|
|
4236
4606
|
"\u2717 ",
|
|
4237
4607
|
error
|
|
4238
4608
|
] }) });
|
|
4239
4609
|
}
|
|
4240
4610
|
if (phase === "loading") {
|
|
4241
|
-
return /* @__PURE__ */
|
|
4611
|
+
return /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "Loading..." });
|
|
4242
4612
|
}
|
|
4243
4613
|
if (!status) return null;
|
|
4244
4614
|
const totalCount = status.backups.count + status.templates.count + status.scripts.count + status.logs.count;
|
|
4245
4615
|
const totalSize = status.backups.totalSize + status.templates.totalSize + status.scripts.totalSize + status.logs.totalSize;
|
|
4246
|
-
const statusDisplay = /* @__PURE__ */
|
|
4247
|
-
/* @__PURE__ */
|
|
4616
|
+
const statusDisplay = /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
|
|
4617
|
+
/* @__PURE__ */ jsxs14(Text14, { bold: true, children: [
|
|
4248
4618
|
"\u25B8 ",
|
|
4249
4619
|
APP_NAME,
|
|
4250
4620
|
" status \u2014 ~/.",
|
|
4251
4621
|
APP_NAME,
|
|
4252
4622
|
"/"
|
|
4253
4623
|
] }),
|
|
4254
|
-
/* @__PURE__ */
|
|
4624
|
+
/* @__PURE__ */ jsx14(Box12, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsx14(
|
|
4255
4625
|
Table,
|
|
4256
4626
|
{
|
|
4257
4627
|
headers: ["Directory", "Count", "Size"],
|
|
@@ -4280,15 +4650,15 @@ var StatusView = ({ cleanup }) => {
|
|
|
4280
4650
|
]
|
|
4281
4651
|
}
|
|
4282
4652
|
) }),
|
|
4283
|
-
status.lastBackup && /* @__PURE__ */
|
|
4284
|
-
/* @__PURE__ */
|
|
4653
|
+
status.lastBackup && /* @__PURE__ */ jsxs14(Box12, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
|
|
4654
|
+
/* @__PURE__ */ jsxs14(Text14, { children: [
|
|
4285
4655
|
"Latest backup: ",
|
|
4286
4656
|
formatDate(status.lastBackup),
|
|
4287
4657
|
" (",
|
|
4288
4658
|
formatRelativeTime(status.lastBackup),
|
|
4289
4659
|
")"
|
|
4290
4660
|
] }),
|
|
4291
|
-
status.oldestBackup && /* @__PURE__ */
|
|
4661
|
+
status.oldestBackup && /* @__PURE__ */ jsxs14(Text14, { children: [
|
|
4292
4662
|
"Oldest backup: ",
|
|
4293
4663
|
formatDate(status.oldestBackup),
|
|
4294
4664
|
" (",
|
|
@@ -4297,18 +4667,18 @@ var StatusView = ({ cleanup }) => {
|
|
|
4297
4667
|
] })
|
|
4298
4668
|
] })
|
|
4299
4669
|
] });
|
|
4300
|
-
const escHint = (action) => /* @__PURE__ */
|
|
4670
|
+
const escHint = (action) => /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
|
|
4301
4671
|
"Press ",
|
|
4302
|
-
/* @__PURE__ */
|
|
4672
|
+
/* @__PURE__ */ jsx14(Text14, { bold: true, children: "ESC" }),
|
|
4303
4673
|
" to ",
|
|
4304
4674
|
action
|
|
4305
4675
|
] }) });
|
|
4306
4676
|
if (phase === "display") {
|
|
4307
|
-
return /* @__PURE__ */
|
|
4677
|
+
return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
|
|
4308
4678
|
statusDisplay,
|
|
4309
|
-
/* @__PURE__ */
|
|
4310
|
-
/* @__PURE__ */
|
|
4311
|
-
/* @__PURE__ */
|
|
4679
|
+
/* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
4680
|
+
/* @__PURE__ */ jsx14(Text14, { bold: true, children: "\u25B8 Actions" }),
|
|
4681
|
+
/* @__PURE__ */ jsx14(
|
|
4312
4682
|
SelectInput3,
|
|
4313
4683
|
{
|
|
4314
4684
|
items: [
|
|
@@ -4356,11 +4726,11 @@ var StatusView = ({ cleanup }) => {
|
|
|
4356
4726
|
value: "cancel"
|
|
4357
4727
|
}
|
|
4358
4728
|
];
|
|
4359
|
-
return /* @__PURE__ */
|
|
4729
|
+
return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
|
|
4360
4730
|
statusDisplay,
|
|
4361
|
-
/* @__PURE__ */
|
|
4362
|
-
/* @__PURE__ */
|
|
4363
|
-
/* @__PURE__ */
|
|
4731
|
+
/* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
4732
|
+
/* @__PURE__ */ jsx14(Text14, { bold: true, children: "\u25B8 Cleanup options" }),
|
|
4733
|
+
/* @__PURE__ */ jsx14(
|
|
4364
4734
|
SelectInput3,
|
|
4365
4735
|
{
|
|
4366
4736
|
items: cleanupItems,
|
|
@@ -4386,26 +4756,26 @@ var StatusView = ({ cleanup }) => {
|
|
|
4386
4756
|
value: "done"
|
|
4387
4757
|
}
|
|
4388
4758
|
];
|
|
4389
|
-
return /* @__PURE__ */
|
|
4759
|
+
return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
|
|
4390
4760
|
statusDisplay,
|
|
4391
|
-
/* @__PURE__ */
|
|
4392
|
-
/* @__PURE__ */
|
|
4393
|
-
selectedForDeletion.length > 0 && /* @__PURE__ */
|
|
4761
|
+
/* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
4762
|
+
/* @__PURE__ */ jsx14(Text14, { bold: true, children: "\u25B8 Select backups to delete" }),
|
|
4763
|
+
selectedForDeletion.length > 0 && /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
|
|
4394
4764
|
" ",
|
|
4395
4765
|
selectedForDeletion.length,
|
|
4396
4766
|
" backup(s) selected (",
|
|
4397
4767
|
formatBytes(selectedForDeletion.reduce((s, b) => s + b.size, 0)),
|
|
4398
4768
|
")"
|
|
4399
4769
|
] }),
|
|
4400
|
-
/* @__PURE__ */
|
|
4770
|
+
/* @__PURE__ */ jsx14(SelectInput3, { items: selectItems, onSelect: handleSelectBackup })
|
|
4401
4771
|
] }),
|
|
4402
4772
|
escHint("go back")
|
|
4403
4773
|
] });
|
|
4404
4774
|
}
|
|
4405
4775
|
if (phase === "confirming") {
|
|
4406
|
-
return /* @__PURE__ */
|
|
4407
|
-
/* @__PURE__ */
|
|
4408
|
-
/* @__PURE__ */
|
|
4776
|
+
return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
|
|
4777
|
+
/* @__PURE__ */ jsx14(Text14, { children: cleanupMessage }),
|
|
4778
|
+
/* @__PURE__ */ jsx14(
|
|
4409
4779
|
Confirm,
|
|
4410
4780
|
{
|
|
4411
4781
|
message: "Proceed?",
|
|
@@ -4416,7 +4786,7 @@ var StatusView = ({ cleanup }) => {
|
|
|
4416
4786
|
] });
|
|
4417
4787
|
}
|
|
4418
4788
|
if (phase === "done") {
|
|
4419
|
-
return /* @__PURE__ */
|
|
4789
|
+
return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx14(Text14, { color: "green", children: "\u2713 Cleanup complete" }) });
|
|
4420
4790
|
}
|
|
4421
4791
|
return null;
|
|
4422
4792
|
};
|
|
@@ -4450,7 +4820,93 @@ function registerStatusCommand(program2) {
|
|
|
4450
4820
|
}
|
|
4451
4821
|
return;
|
|
4452
4822
|
}
|
|
4453
|
-
const { waitUntilExit } =
|
|
4823
|
+
const { waitUntilExit } = render10(/* @__PURE__ */ jsx14(StatusView, { cleanup: opts.cleanup }));
|
|
4824
|
+
await waitUntilExit();
|
|
4825
|
+
});
|
|
4826
|
+
}
|
|
4827
|
+
|
|
4828
|
+
// src/commands/Unlink.tsx
|
|
4829
|
+
import { Box as Box13, Text as Text15, useApp as useApp10 } from "ink";
|
|
4830
|
+
import { render as render11 } from "ink";
|
|
4831
|
+
import { useEffect as useEffect10, useState as useState11 } from "react";
|
|
4832
|
+
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4833
|
+
var UnlinkView = ({ clean }) => {
|
|
4834
|
+
const { exit } = useApp10();
|
|
4835
|
+
const [phase, setPhase] = useState11("unlinking");
|
|
4836
|
+
const [result, setResult] = useState11(null);
|
|
4837
|
+
const [error, setError] = useState11(null);
|
|
4838
|
+
useEffect10(() => {
|
|
4839
|
+
(async () => {
|
|
4840
|
+
try {
|
|
4841
|
+
const unlinkResult = await unlinkSyncpoint({ clean });
|
|
4842
|
+
setResult(unlinkResult);
|
|
4843
|
+
setPhase("done");
|
|
4844
|
+
setTimeout(() => exit(), 100);
|
|
4845
|
+
} catch (err) {
|
|
4846
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4847
|
+
setPhase("error");
|
|
4848
|
+
setTimeout(() => exit(), 100);
|
|
4849
|
+
}
|
|
4850
|
+
})();
|
|
4851
|
+
}, []);
|
|
4852
|
+
if (phase === "error" || error) {
|
|
4853
|
+
return /* @__PURE__ */ jsx15(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
|
|
4854
|
+
"\u2717 Unlink failed: ",
|
|
4855
|
+
error
|
|
4856
|
+
] }) });
|
|
4857
|
+
}
|
|
4858
|
+
if (phase === "unlinking") {
|
|
4859
|
+
return /* @__PURE__ */ jsx15(Box13, { children: /* @__PURE__ */ jsx15(Text15, { children: "\u25B8 Restoring ~/.syncpoint from destination..." }) });
|
|
4860
|
+
}
|
|
4861
|
+
return /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
|
|
4862
|
+
/* @__PURE__ */ jsx15(Text15, { color: "green", bold: true, children: "\u2713 Unlink complete" }),
|
|
4863
|
+
result && /* @__PURE__ */ jsxs15(Fragment3, { children: [
|
|
4864
|
+
/* @__PURE__ */ jsxs15(Text15, { children: [
|
|
4865
|
+
" ",
|
|
4866
|
+
"Restored: ",
|
|
4867
|
+
contractTilde(result.appDir)
|
|
4868
|
+
] }),
|
|
4869
|
+
/* @__PURE__ */ jsxs15(Text15, { children: [
|
|
4870
|
+
" ",
|
|
4871
|
+
"Source: ",
|
|
4872
|
+
contractTilde(result.targetDir)
|
|
4873
|
+
] }),
|
|
4874
|
+
result.cleaned && /* @__PURE__ */ jsxs15(Text15, { color: "yellow", children: [
|
|
4875
|
+
" ",
|
|
4876
|
+
"Destination copy removed (--clean)"
|
|
4877
|
+
] })
|
|
4878
|
+
] })
|
|
4879
|
+
] });
|
|
4880
|
+
};
|
|
4881
|
+
function registerUnlinkCommand(program2) {
|
|
4882
|
+
const cmdInfo = COMMANDS.unlink;
|
|
4883
|
+
const cmd = program2.command("unlink").description(cmdInfo.description);
|
|
4884
|
+
cmdInfo.options?.forEach((opt) => {
|
|
4885
|
+
cmd.option(opt.flag, opt.description);
|
|
4886
|
+
});
|
|
4887
|
+
cmd.action(async (opts) => {
|
|
4888
|
+
const globalOpts = program2.opts();
|
|
4889
|
+
const startTime = Date.now();
|
|
4890
|
+
if (globalOpts.json) {
|
|
4891
|
+
try {
|
|
4892
|
+
const unlinkResult = await unlinkSyncpoint({ clean: opts.clean });
|
|
4893
|
+
respond(
|
|
4894
|
+
"unlink",
|
|
4895
|
+
{
|
|
4896
|
+
appDir: unlinkResult.appDir,
|
|
4897
|
+
targetDir: unlinkResult.targetDir,
|
|
4898
|
+
cleaned: unlinkResult.cleaned
|
|
4899
|
+
},
|
|
4900
|
+
startTime,
|
|
4901
|
+
VERSION
|
|
4902
|
+
);
|
|
4903
|
+
} catch (error) {
|
|
4904
|
+
const code = classifyError(error);
|
|
4905
|
+
respondError("unlink", code, error.message, startTime, VERSION);
|
|
4906
|
+
}
|
|
4907
|
+
return;
|
|
4908
|
+
}
|
|
4909
|
+
const { waitUntilExit } = render11(/* @__PURE__ */ jsx15(UnlinkView, { clean: opts.clean ?? false }));
|
|
4454
4910
|
await waitUntilExit();
|
|
4455
4911
|
});
|
|
4456
4912
|
}
|
|
@@ -4459,15 +4915,15 @@ function registerStatusCommand(program2) {
|
|
|
4459
4915
|
import {
|
|
4460
4916
|
copyFile as copyFile3,
|
|
4461
4917
|
readFile as readFile6,
|
|
4462
|
-
rename,
|
|
4463
|
-
unlink,
|
|
4918
|
+
rename as rename2,
|
|
4919
|
+
unlink as unlink2,
|
|
4464
4920
|
writeFile as writeFile5
|
|
4465
4921
|
} from "fs/promises";
|
|
4466
|
-
import { join as
|
|
4467
|
-
import { Box as
|
|
4468
|
-
import { render as
|
|
4922
|
+
import { join as join15 } from "path";
|
|
4923
|
+
import { Box as Box14, Text as Text16, useApp as useApp11 } from "ink";
|
|
4924
|
+
import { render as render12 } from "ink";
|
|
4469
4925
|
import Spinner3 from "ink-spinner";
|
|
4470
|
-
import { useEffect as
|
|
4926
|
+
import { useEffect as useEffect11, useState as useState12 } from "react";
|
|
4471
4927
|
|
|
4472
4928
|
// src/prompts/wizard-config.ts
|
|
4473
4929
|
function generateConfigWizardPrompt(variables) {
|
|
@@ -4511,8 +4967,8 @@ ${variables.defaultConfig}
|
|
|
4511
4967
|
init_assets();
|
|
4512
4968
|
|
|
4513
4969
|
// src/utils/file-scanner.ts
|
|
4514
|
-
import { stat as
|
|
4515
|
-
import { join as
|
|
4970
|
+
import { stat as stat4 } from "fs/promises";
|
|
4971
|
+
import { join as join14 } from "path";
|
|
4516
4972
|
import glob from "fast-glob";
|
|
4517
4973
|
var FILE_CATEGORIES = {
|
|
4518
4974
|
shell: {
|
|
@@ -4591,8 +5047,8 @@ async function scanHomeDirectory(options) {
|
|
|
4591
5047
|
const validFiles = [];
|
|
4592
5048
|
for (const file of files) {
|
|
4593
5049
|
try {
|
|
4594
|
-
const fullPath =
|
|
4595
|
-
await
|
|
5050
|
+
const fullPath = join14(homeDir, file);
|
|
5051
|
+
await stat4(fullPath);
|
|
4596
5052
|
validFiles.push(file);
|
|
4597
5053
|
categorizedFiles.add(file);
|
|
4598
5054
|
} catch {
|
|
@@ -4626,8 +5082,8 @@ async function scanHomeDirectory(options) {
|
|
|
4626
5082
|
if (categorizedFiles.has(file)) continue;
|
|
4627
5083
|
if (totalFiles >= maxFiles) break;
|
|
4628
5084
|
try {
|
|
4629
|
-
const fullPath =
|
|
4630
|
-
await
|
|
5085
|
+
const fullPath = join14(homeDir, file);
|
|
5086
|
+
await stat4(fullPath);
|
|
4631
5087
|
uncategorizedFiles.push(file);
|
|
4632
5088
|
totalFiles++;
|
|
4633
5089
|
} catch {
|
|
@@ -4651,7 +5107,7 @@ async function scanHomeDirectory(options) {
|
|
|
4651
5107
|
}
|
|
4652
5108
|
|
|
4653
5109
|
// src/commands/Wizard.tsx
|
|
4654
|
-
import { jsx as
|
|
5110
|
+
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
4655
5111
|
var MAX_RETRIES2 = 3;
|
|
4656
5112
|
async function restoreBackup2(configPath) {
|
|
4657
5113
|
const bakPath = `${configPath}.bak`;
|
|
@@ -4701,21 +5157,21 @@ ${formatValidationErrors(validation.errors || [])}`
|
|
|
4701
5157
|
}
|
|
4702
5158
|
}
|
|
4703
5159
|
var WizardView = ({ printMode }) => {
|
|
4704
|
-
const { exit } =
|
|
4705
|
-
const [phase, setPhase] =
|
|
4706
|
-
const [message, setMessage] =
|
|
4707
|
-
const [error, setError] =
|
|
4708
|
-
const [prompt, setPrompt] =
|
|
4709
|
-
const [sessionId, setSessionId] =
|
|
4710
|
-
const [attemptNumber, setAttemptNumber] =
|
|
4711
|
-
|
|
5160
|
+
const { exit } = useApp11();
|
|
5161
|
+
const [phase, setPhase] = useState12("init");
|
|
5162
|
+
const [message, setMessage] = useState12("");
|
|
5163
|
+
const [error, setError] = useState12(null);
|
|
5164
|
+
const [prompt, setPrompt] = useState12("");
|
|
5165
|
+
const [sessionId, setSessionId] = useState12(void 0);
|
|
5166
|
+
const [attemptNumber, setAttemptNumber] = useState12(1);
|
|
5167
|
+
useEffect11(() => {
|
|
4712
5168
|
(async () => {
|
|
4713
5169
|
try {
|
|
4714
|
-
const configPath =
|
|
5170
|
+
const configPath = join15(getAppDir(), CONFIG_FILENAME);
|
|
4715
5171
|
if (await fileExists(configPath)) {
|
|
4716
5172
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4717
5173
|
const bakPath = `${configPath}.${timestamp2}.bak`;
|
|
4718
|
-
await
|
|
5174
|
+
await rename2(configPath, bakPath);
|
|
4719
5175
|
setMessage(`Backed up existing config to ${bakPath}`);
|
|
4720
5176
|
}
|
|
4721
5177
|
setPhase("scanning");
|
|
@@ -4781,9 +5237,9 @@ var WizardView = ({ printMode }) => {
|
|
|
4781
5237
|
await writeFile5(tmpPath, yamlContent, "utf-8");
|
|
4782
5238
|
const verification = validateConfig(parseYAML(yamlContent));
|
|
4783
5239
|
if (verification.valid) {
|
|
4784
|
-
await
|
|
5240
|
+
await rename2(tmpPath, configPath);
|
|
4785
5241
|
} else {
|
|
4786
|
-
await
|
|
5242
|
+
await unlink2(tmpPath);
|
|
4787
5243
|
throw new Error("Final validation failed");
|
|
4788
5244
|
}
|
|
4789
5245
|
setPhase("done");
|
|
@@ -4822,37 +5278,37 @@ ${formatValidationErrors(validation.errors || [])}`
|
|
|
4822
5278
|
}
|
|
4823
5279
|
}
|
|
4824
5280
|
if (error) {
|
|
4825
|
-
return /* @__PURE__ */
|
|
5281
|
+
return /* @__PURE__ */ jsx16(Box14, { flexDirection: "column", children: /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
|
|
4826
5282
|
"\u2717 ",
|
|
4827
5283
|
error
|
|
4828
5284
|
] }) });
|
|
4829
5285
|
}
|
|
4830
5286
|
if (printMode && phase === "done") {
|
|
4831
|
-
return /* @__PURE__ */
|
|
4832
|
-
/* @__PURE__ */
|
|
4833
|
-
/* @__PURE__ */
|
|
4834
|
-
/* @__PURE__ */
|
|
4835
|
-
/* @__PURE__ */
|
|
4836
|
-
/* @__PURE__ */
|
|
5287
|
+
return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
|
|
5288
|
+
/* @__PURE__ */ jsx16(Text16, { bold: true, children: "Config Wizard Prompt (Copy and paste to your LLM):" }),
|
|
5289
|
+
/* @__PURE__ */ jsx16(Box14, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u2500".repeat(60) }) }),
|
|
5290
|
+
/* @__PURE__ */ jsx16(Text16, { children: prompt }),
|
|
5291
|
+
/* @__PURE__ */ jsx16(Box14, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u2500".repeat(60) }) }),
|
|
5292
|
+
/* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "After getting the YAML response, save it to ~/.syncpoint/config.yml" })
|
|
4837
5293
|
] });
|
|
4838
5294
|
}
|
|
4839
5295
|
if (phase === "done") {
|
|
4840
|
-
return /* @__PURE__ */
|
|
4841
|
-
/* @__PURE__ */
|
|
4842
|
-
/* @__PURE__ */
|
|
4843
|
-
/* @__PURE__ */
|
|
4844
|
-
/* @__PURE__ */
|
|
4845
|
-
/* @__PURE__ */
|
|
5296
|
+
return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
|
|
5297
|
+
/* @__PURE__ */ jsx16(Text16, { color: "green", children: message }),
|
|
5298
|
+
/* @__PURE__ */ jsxs16(Box14, { marginTop: 1, children: [
|
|
5299
|
+
/* @__PURE__ */ jsx16(Text16, { children: "Next steps:" }),
|
|
5300
|
+
/* @__PURE__ */ jsx16(Text16, { children: " 1. Review your config: ~/.syncpoint/config.yml" }),
|
|
5301
|
+
/* @__PURE__ */ jsx16(Text16, { children: " 2. Run: syncpoint backup" })
|
|
4846
5302
|
] })
|
|
4847
5303
|
] });
|
|
4848
5304
|
}
|
|
4849
|
-
return /* @__PURE__ */
|
|
4850
|
-
/* @__PURE__ */
|
|
4851
|
-
/* @__PURE__ */
|
|
5305
|
+
return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
|
|
5306
|
+
/* @__PURE__ */ jsxs16(Text16, { children: [
|
|
5307
|
+
/* @__PURE__ */ jsx16(Text16, { color: "cyan", children: /* @__PURE__ */ jsx16(Spinner3, { type: "dots" }) }),
|
|
4852
5308
|
" ",
|
|
4853
5309
|
message
|
|
4854
5310
|
] }),
|
|
4855
|
-
attemptNumber > 1 && /* @__PURE__ */
|
|
5311
|
+
attemptNumber > 1 && /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
|
|
4856
5312
|
"Attempt ",
|
|
4857
5313
|
attemptNumber,
|
|
4858
5314
|
"/",
|
|
@@ -4889,17 +5345,17 @@ function registerWizardCommand(program2) {
|
|
|
4889
5345
|
return;
|
|
4890
5346
|
}
|
|
4891
5347
|
if (opts.print) {
|
|
4892
|
-
const { waitUntilExit } =
|
|
5348
|
+
const { waitUntilExit } = render12(/* @__PURE__ */ jsx16(WizardView, { printMode: true }));
|
|
4893
5349
|
await waitUntilExit();
|
|
4894
5350
|
return;
|
|
4895
5351
|
}
|
|
4896
|
-
const configPath =
|
|
5352
|
+
const configPath = join15(getAppDir(), CONFIG_FILENAME);
|
|
4897
5353
|
try {
|
|
4898
5354
|
if (await fileExists(configPath)) {
|
|
4899
5355
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4900
5356
|
const bakPath = `${configPath}.${timestamp2}.bak`;
|
|
4901
5357
|
console.log(`\u{1F4CB} Backing up existing config to ${bakPath}`);
|
|
4902
|
-
await
|
|
5358
|
+
await rename2(configPath, bakPath);
|
|
4903
5359
|
}
|
|
4904
5360
|
if (!await isClaudeCodeAvailable()) {
|
|
4905
5361
|
throw new Error(
|
|
@@ -4934,15 +5390,37 @@ registerCreateTemplateCommand(program);
|
|
|
4934
5390
|
registerListCommand(program);
|
|
4935
5391
|
registerMigrateCommand(program);
|
|
4936
5392
|
registerStatusCommand(program);
|
|
5393
|
+
registerLinkCommand(program);
|
|
5394
|
+
registerUnlinkCommand(program);
|
|
4937
5395
|
registerHelpCommand(program);
|
|
4938
5396
|
if (process.argv.includes("--describe")) {
|
|
4939
5397
|
const startTime = Date.now();
|
|
4940
5398
|
const globalOptions = [
|
|
4941
|
-
{
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
5399
|
+
{
|
|
5400
|
+
flag: "--json",
|
|
5401
|
+
description: "Output structured JSON to stdout",
|
|
5402
|
+
type: "boolean"
|
|
5403
|
+
},
|
|
5404
|
+
{
|
|
5405
|
+
flag: "--yes",
|
|
5406
|
+
description: "Skip confirmation prompts (non-interactive mode)",
|
|
5407
|
+
type: "boolean"
|
|
5408
|
+
},
|
|
5409
|
+
{
|
|
5410
|
+
flag: "--describe",
|
|
5411
|
+
description: "Print CLI schema as JSON and exit",
|
|
5412
|
+
type: "boolean"
|
|
5413
|
+
},
|
|
5414
|
+
{
|
|
5415
|
+
flag: "-V, --version",
|
|
5416
|
+
description: "Output the version number",
|
|
5417
|
+
type: "boolean"
|
|
5418
|
+
},
|
|
5419
|
+
{
|
|
5420
|
+
flag: "-h, --help",
|
|
5421
|
+
description: "Display help for command",
|
|
5422
|
+
type: "boolean"
|
|
5423
|
+
}
|
|
4946
5424
|
];
|
|
4947
5425
|
respond(
|
|
4948
5426
|
"describe",
|
|
@@ -4960,7 +5438,13 @@ if (process.argv.includes("--describe")) {
|
|
|
4960
5438
|
}
|
|
4961
5439
|
program.parseAsync(process.argv).catch((error) => {
|
|
4962
5440
|
if (process.argv.includes("--json")) {
|
|
4963
|
-
respondError(
|
|
5441
|
+
respondError(
|
|
5442
|
+
"unknown",
|
|
5443
|
+
SyncpointErrorCode.UNKNOWN,
|
|
5444
|
+
error.message,
|
|
5445
|
+
Date.now(),
|
|
5446
|
+
VERSION
|
|
5447
|
+
);
|
|
4964
5448
|
process.exit(1);
|
|
4965
5449
|
}
|
|
4966
5450
|
console.error("Fatal error:", error.message);
|