@lumy-pack/syncpoint 0.0.3 → 0.0.4

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 CHANGED
@@ -193,7 +193,7 @@ function generateFilename(pattern, options) {
193
193
  import { appendFile, mkdir as mkdir2 } from "fs/promises";
194
194
  import { join as join3 } from "path";
195
195
  import pc from "picocolors";
196
- var ANSI_RE = /\x1b\[[0-9;]*m/g;
196
+ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
197
197
  function stripAnsi(str) {
198
198
  return str.replace(ANSI_RE, "");
199
199
  }
@@ -334,7 +334,7 @@ function isValidPattern(pattern) {
334
334
 
335
335
  // src/core/metadata.ts
336
336
  import { createHash } from "crypto";
337
- import { lstat, readFile } from "fs/promises";
337
+ import { lstat, readFile, readlink } from "fs/promises";
338
338
 
339
339
  // src/schemas/ajv.ts
340
340
  import Ajv from "ajv";
@@ -426,7 +426,7 @@ function validateMetadata(data) {
426
426
  }
427
427
 
428
428
  // src/version.ts
429
- var VERSION = "0.0.3";
429
+ var VERSION = "0.0.4";
430
430
 
431
431
  // src/core/metadata.ts
432
432
  var METADATA_VERSION = "1.0.0";
@@ -477,7 +477,8 @@ async function collectFileInfo(absolutePath, logicalPath) {
477
477
  }
478
478
  let hash;
479
479
  if (lstats.isSymbolicLink()) {
480
- hash = `sha256:${createHash("sha256").update(absolutePath).digest("hex")}`;
480
+ const linkTarget = await readlink(absolutePath);
481
+ hash = `sha256:${createHash("sha256").update(linkTarget).digest("hex")}`;
481
482
  } else {
482
483
  hash = await computeFileHash(absolutePath);
483
484
  }
@@ -538,7 +539,8 @@ async function extractArchive(archivePath, destDir) {
538
539
  const normalizedPath = normalize2(path);
539
540
  if (normalizedPath.includes("..")) return false;
540
541
  if (normalizedPath.startsWith("/")) return false;
541
- if (entry.type === "SymbolicLink" || entry.type === "Link") return false;
542
+ if ("type" in entry && (entry.type === "SymbolicLink" || entry.type === "Link"))
543
+ return false;
542
544
  return true;
543
545
  }
544
546
  });
@@ -723,7 +725,7 @@ async function createBackup(config, options = {}) {
723
725
  }
724
726
  }
725
727
  let allFiles = [...found];
726
- if (config.scripts.includeInBackup) {
728
+ if (config.scripts?.includeInBackup) {
727
729
  const scripts = await collectScripts();
728
730
  allFiles = [...allFiles, ...scripts];
729
731
  }
@@ -1081,6 +1083,7 @@ var BackupView = ({ options }) => {
1081
1083
  const [error, setError] = useState(null);
1082
1084
  useEffect(() => {
1083
1085
  (async () => {
1086
+ let progressInterval;
1084
1087
  try {
1085
1088
  const cfg = await loadConfig();
1086
1089
  setConfig(cfg);
@@ -1093,7 +1096,7 @@ var BackupView = ({ options }) => {
1093
1096
  return;
1094
1097
  }
1095
1098
  setPhase("compressing");
1096
- const progressInterval = setInterval(() => {
1099
+ progressInterval = setInterval(() => {
1097
1100
  setProgress((prev) => {
1098
1101
  if (prev >= 90) return prev;
1099
1102
  return prev + 10;
@@ -1106,9 +1109,10 @@ var BackupView = ({ options }) => {
1106
1109
  setPhase("done");
1107
1110
  setTimeout(() => exit(), 100);
1108
1111
  } catch (err) {
1112
+ if (progressInterval) clearInterval(progressInterval);
1109
1113
  setError(err instanceof Error ? err.message : String(err));
1110
1114
  setPhase("error");
1111
- exit();
1115
+ setTimeout(() => exit(), 100);
1112
1116
  }
1113
1117
  })();
1114
1118
  }, []);
@@ -1162,7 +1166,7 @@ var BackupView = ({ options }) => {
1162
1166
  /* @__PURE__ */ jsxs2(Text2, { children: [
1163
1167
  " ",
1164
1168
  "File: ",
1165
- result.metadata.config.filename
1169
+ result.archivePath.split("/").pop()
1166
1170
  ] }),
1167
1171
  /* @__PURE__ */ jsxs2(Text2, { children: [
1168
1172
  " ",
@@ -1199,8 +1203,8 @@ import { useState as useState2, useEffect as useEffect2 } from "react";
1199
1203
  import { Text as Text3, Box as Box2, useApp as useApp2 } from "ink";
1200
1204
  import Spinner from "ink-spinner";
1201
1205
  import { render as render2 } from "ink";
1202
- import { join as join9 } from "path";
1203
- import { writeFile as writeFile4 } from "fs/promises";
1206
+ import { join as join8 } from "path";
1207
+ import { writeFile as writeFile3 } from "fs/promises";
1204
1208
 
1205
1209
  // src/schemas/template.schema.ts
1206
1210
  var templateSchema = {
@@ -1282,9 +1286,6 @@ Begin by asking the user to describe their provisioning needs.`;
1282
1286
 
1283
1287
  // src/utils/claude-code-runner.ts
1284
1288
  import { spawn } from "child_process";
1285
- import { unlink, writeFile as writeFile3 } from "fs/promises";
1286
- import { tmpdir as tmpdir2 } from "os";
1287
- import { join as join8 } from "path";
1288
1289
  async function isClaudeCodeAvailable() {
1289
1290
  return new Promise((resolve2) => {
1290
1291
  const child = spawn("which", ["claude"], { shell: true });
@@ -1298,58 +1299,49 @@ async function isClaudeCodeAvailable() {
1298
1299
  }
1299
1300
  async function invokeClaudeCode(prompt, options) {
1300
1301
  const timeout = options?.timeout ?? 12e4;
1301
- const promptFile = join8(tmpdir2(), `syncpoint-prompt-${Date.now()}.txt`);
1302
- await writeFile3(promptFile, prompt, "utf-8");
1303
- try {
1304
- return await new Promise((resolve2, reject) => {
1305
- const args = ["--permission-mode", "acceptEdits", "--model", "sonnet"];
1306
- if (options?.sessionId) {
1307
- args.push("--session", options.sessionId);
1302
+ return await new Promise((resolve2, reject) => {
1303
+ const args = ["--permission-mode", "acceptEdits", "--model", "sonnet"];
1304
+ if (options?.sessionId) {
1305
+ args.push("--session", options.sessionId);
1306
+ }
1307
+ const child = spawn("claude", args, {
1308
+ stdio: ["pipe", "pipe", "pipe"]
1309
+ });
1310
+ let stdout = "";
1311
+ let stderr = "";
1312
+ child.stdout.on("data", (data) => {
1313
+ stdout += data.toString();
1314
+ });
1315
+ child.stderr.on("data", (data) => {
1316
+ stderr += data.toString();
1317
+ });
1318
+ child.stdin.write(prompt);
1319
+ child.stdin.end();
1320
+ const timer = setTimeout(() => {
1321
+ child.kill();
1322
+ reject(new Error(`Claude Code invocation timeout after ${timeout}ms`));
1323
+ }, timeout);
1324
+ child.on("close", (code) => {
1325
+ clearTimeout(timer);
1326
+ if (code === 0) {
1327
+ resolve2({
1328
+ success: true,
1329
+ output: stdout,
1330
+ sessionId: options?.sessionId
1331
+ });
1332
+ } else {
1333
+ resolve2({
1334
+ success: false,
1335
+ output: stdout,
1336
+ error: stderr || `Process exited with code ${code}`
1337
+ });
1308
1338
  }
1309
- const child = spawn("claude", args, {
1310
- stdio: ["pipe", "pipe", "pipe"]
1311
- });
1312
- let stdout = "";
1313
- let stderr = "";
1314
- child.stdout.on("data", (data) => {
1315
- stdout += data.toString();
1316
- });
1317
- child.stderr.on("data", (data) => {
1318
- stderr += data.toString();
1319
- });
1320
- child.stdin.write(prompt);
1321
- child.stdin.end();
1322
- const timer = setTimeout(() => {
1323
- child.kill();
1324
- reject(new Error(`Claude Code invocation timeout after ${timeout}ms`));
1325
- }, timeout);
1326
- child.on("close", (code) => {
1327
- clearTimeout(timer);
1328
- if (code === 0) {
1329
- resolve2({
1330
- success: true,
1331
- output: stdout,
1332
- sessionId: options?.sessionId
1333
- });
1334
- } else {
1335
- resolve2({
1336
- success: false,
1337
- output: stdout,
1338
- error: stderr || `Process exited with code ${code}`
1339
- });
1340
- }
1341
- });
1342
- child.on("error", (err) => {
1343
- clearTimeout(timer);
1344
- reject(err);
1345
- });
1346
1339
  });
1347
- } finally {
1348
- try {
1349
- await unlink(promptFile);
1350
- } catch {
1351
- }
1352
- }
1340
+ child.on("error", (err) => {
1341
+ clearTimeout(timer);
1342
+ reject(err);
1343
+ });
1344
+ });
1353
1345
  }
1354
1346
  async function resumeClaudeCodeSession(sessionId, prompt, options) {
1355
1347
  return invokeClaudeCode(prompt, {
@@ -1533,14 +1525,14 @@ var CreateTemplateView = ({
1533
1525
  setPhase("writing");
1534
1526
  setMessage("Writing template...");
1535
1527
  const filename = templateName ? `${templateName}.yml` : `${parsedTemplate.name.toLowerCase().replace(/\s+/g, "-")}.yml`;
1536
- const templatePath = join9(templatesDir, filename);
1528
+ const templatePath = join8(templatesDir, filename);
1537
1529
  if (await fileExists(templatePath)) {
1538
1530
  throw new Error(
1539
1531
  `Template already exists: ${filename}
1540
1532
  Please choose a different name or delete the existing template.`
1541
1533
  );
1542
1534
  }
1543
- await writeFile4(templatePath, yamlContent, "utf-8");
1535
+ await writeFile3(templatePath, yamlContent, "utf-8");
1544
1536
  setPhase("done");
1545
1537
  setMessage(`\u2713 Template created: ${filename}`);
1546
1538
  setTimeout(() => exit(), 100);
@@ -1766,7 +1758,7 @@ function registerHelpCommand(program2) {
1766
1758
  import { useState as useState3, useEffect as useEffect3 } from "react";
1767
1759
  import { Text as Text5, Box as Box4, useApp as useApp3 } from "ink";
1768
1760
  import { render as render4 } from "ink";
1769
- import { join as join10 } from "path";
1761
+ import { join as join9 } from "path";
1770
1762
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1771
1763
  var InitView = () => {
1772
1764
  const { exit } = useApp3();
@@ -1777,7 +1769,7 @@ var InitView = () => {
1777
1769
  (async () => {
1778
1770
  try {
1779
1771
  const appDir = getAppDir();
1780
- if (await fileExists(join10(appDir, CONFIG_FILENAME))) {
1772
+ if (await fileExists(join9(appDir, CONFIG_FILENAME))) {
1781
1773
  setError(`Already initialized: ${appDir}`);
1782
1774
  exit();
1783
1775
  return;
@@ -1810,11 +1802,11 @@ var InitView = () => {
1810
1802
  await initDefaultConfig();
1811
1803
  completed.push({ name: `Created ${CONFIG_FILENAME} (defaults)`, done: true });
1812
1804
  setSteps([...completed]);
1813
- const exampleTemplatePath = join10(getSubDir(TEMPLATES_DIR), "example.yml");
1805
+ const exampleTemplatePath = join9(getSubDir(TEMPLATES_DIR), "example.yml");
1814
1806
  if (!await fileExists(exampleTemplatePath)) {
1815
- const { writeFile: writeFile6 } = await import("fs/promises");
1807
+ const { writeFile: writeFile5 } = await import("fs/promises");
1816
1808
  const exampleYaml = readAsset("template.example.yml");
1817
- await writeFile6(exampleTemplatePath, exampleYaml, "utf-8");
1809
+ await writeFile5(exampleTemplatePath, exampleYaml, "utf-8");
1818
1810
  completed.push({ name: `Created templates/example.yml`, done: true });
1819
1811
  setSteps([...completed]);
1820
1812
  }
@@ -1946,7 +1938,7 @@ var Table = ({
1946
1938
  // src/core/provision.ts
1947
1939
  import { exec } from "child_process";
1948
1940
  import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
1949
- import { join as join11 } from "path";
1941
+ import { join as join10 } from "path";
1950
1942
  import YAML3 from "yaml";
1951
1943
  var REMOTE_SCRIPT_PATTERNS = [
1952
1944
  /curl\s.*\|\s*(ba)?sh/,
@@ -1986,7 +1978,7 @@ async function listTemplates() {
1986
1978
  if (!entry.isFile() || !entry.name.endsWith(".yml") && !entry.name.endsWith(".yaml")) {
1987
1979
  continue;
1988
1980
  }
1989
- const fullPath = join11(templatesDir, entry.name);
1981
+ const fullPath = join10(templatesDir, entry.name);
1990
1982
  try {
1991
1983
  const config = await loadTemplate(fullPath);
1992
1984
  templates.push({
@@ -2063,8 +2055,10 @@ async function executeStep(step) {
2063
2055
  output: output || void 0
2064
2056
  };
2065
2057
  } catch (err) {
2066
- const error = err;
2067
- const errorOutput = [error.stdout, error.stderr, error.message].filter(Boolean).join("\n").trim();
2058
+ const error = err instanceof Error ? err : new Error(String(err));
2059
+ const stdout = err?.stdout ?? "";
2060
+ const stderr = err?.stderr ?? "";
2061
+ const errorOutput = [stdout, stderr, error.message].filter(Boolean).join("\n").trim();
2068
2062
  return {
2069
2063
  name: step.name,
2070
2064
  status: "failed",
@@ -2103,7 +2097,7 @@ async function* runProvision(templatePath, options = {}) {
2103
2097
 
2104
2098
  // src/core/restore.ts
2105
2099
  import { copyFile, lstat as lstat2, readdir as readdir3, stat as stat2 } from "fs/promises";
2106
- import { dirname as dirname2, join as join12 } from "path";
2100
+ import { dirname as dirname2, join as join11 } from "path";
2107
2101
  async function getBackupList(config) {
2108
2102
  const backupDir = config?.backup.destination ? resolveTargetPath(config.backup.destination) : getSubDir(BACKUPS_DIR);
2109
2103
  const exists = await fileExists(backupDir);
@@ -2112,7 +2106,7 @@ async function getBackupList(config) {
2112
2106
  const backups = [];
2113
2107
  for (const entry of entries) {
2114
2108
  if (!entry.isFile() || !entry.name.endsWith(".tar.gz")) continue;
2115
- const fullPath = join12(backupDir, entry.name);
2109
+ const fullPath = join11(backupDir, entry.name);
2116
2110
  const fileStat = await stat2(fullPath);
2117
2111
  let hostname;
2118
2112
  let fileCount;
@@ -2187,7 +2181,7 @@ async function createSafetyBackup(filePaths) {
2187
2181
  const filename = `_pre-restore_${formatDatetime(now)}.tar.gz`;
2188
2182
  const backupDir = getSubDir(BACKUPS_DIR);
2189
2183
  await ensureDir(backupDir);
2190
- const archivePath = join12(backupDir, filename);
2184
+ const archivePath = join11(backupDir, filename);
2191
2185
  const files = [];
2192
2186
  for (const fp of filePaths) {
2193
2187
  const absPath = resolveTargetPath(fp);
@@ -2221,8 +2215,8 @@ async function restoreBackup(archivePath, options = {}) {
2221
2215
  };
2222
2216
  }
2223
2217
  const { mkdtemp: mkdtemp2, rm: rm2 } = await import("fs/promises");
2224
- const { tmpdir: tmpdir3 } = await import("os");
2225
- const tmpDir = await mkdtemp2(join12(tmpdir3(), "syncpoint-restore-"));
2218
+ const { tmpdir: tmpdir2 } = await import("os");
2219
+ const tmpDir = await mkdtemp2(join11(tmpdir2(), "syncpoint-restore-"));
2226
2220
  try {
2227
2221
  await extractArchive(archivePath, tmpDir);
2228
2222
  for (const action of plan.actions) {
@@ -2231,7 +2225,7 @@ async function restoreBackup(archivePath, options = {}) {
2231
2225
  continue;
2232
2226
  }
2233
2227
  const archiveName = action.path.startsWith("~/") ? action.path.slice(2) : action.path;
2234
- const extractedPath = join12(tmpDir, archiveName);
2228
+ const extractedPath = join11(tmpDir, archiveName);
2235
2229
  const destPath = resolveTargetPath(action.path);
2236
2230
  const extractedExists = await fileExists(extractedPath);
2237
2231
  if (!extractedExists) {
@@ -2626,6 +2620,10 @@ var ListView = ({ type, deleteIndex }) => {
2626
2620
  function registerListCommand(program2) {
2627
2621
  program2.command("list [type]").description("List backups and templates").option("--delete <n>", "Delete item #n").action(async (type, opts) => {
2628
2622
  const deleteIndex = opts.delete ? parseInt(opts.delete, 10) : void 0;
2623
+ if (deleteIndex !== void 0 && isNaN(deleteIndex)) {
2624
+ console.error(`Invalid delete index: ${opts.delete}`);
2625
+ process.exit(1);
2626
+ }
2629
2627
  const { waitUntilExit } = render5(
2630
2628
  /* @__PURE__ */ jsx8(ListView, { type, deleteIndex })
2631
2629
  );
@@ -2779,6 +2777,18 @@ var ProvisionView = ({
2779
2777
  stepIdx++;
2780
2778
  }
2781
2779
  }
2780
+ if (template.backup && !options.skipRestore) {
2781
+ try {
2782
+ const backups = await getBackupList();
2783
+ const match = backups.find(
2784
+ (b) => b.filename.includes(template.backup)
2785
+ );
2786
+ if (match) {
2787
+ await restoreBackup(match.path);
2788
+ }
2789
+ } catch {
2790
+ }
2791
+ }
2782
2792
  setPhase("done");
2783
2793
  setTimeout(() => exit(), 100);
2784
2794
  } catch (err) {
@@ -2951,7 +2961,8 @@ var RestoreView = ({ filename, options }) => {
2951
2961
  useEffect6(() => {
2952
2962
  (async () => {
2953
2963
  try {
2954
- const list2 = await getBackupList();
2964
+ const config = await loadConfig();
2965
+ const list2 = await getBackupList(config);
2955
2966
  setBackups(list2);
2956
2967
  if (list2.length === 0) {
2957
2968
  setError("No backups available.");
@@ -3164,7 +3175,7 @@ function registerRestoreCommand(program2) {
3164
3175
 
3165
3176
  // src/commands/Status.tsx
3166
3177
  import { readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
3167
- import { join as join13 } from "path";
3178
+ import { join as join12 } from "path";
3168
3179
  import { Box as Box10, Text as Text12, useApp as useApp7, useInput as useInput3 } from "ink";
3169
3180
  import { render as render8 } from "ink";
3170
3181
  import SelectInput3 from "ink-select-input";
@@ -3177,7 +3188,7 @@ function getDirStats(dirPath) {
3177
3188
  let count = 0;
3178
3189
  for (const entry of entries) {
3179
3190
  try {
3180
- const stat4 = statSync(join13(dirPath, entry));
3191
+ const stat4 = statSync(join12(dirPath, entry));
3181
3192
  if (stat4.isFile()) {
3182
3193
  totalSize += stat4.size;
3183
3194
  count++;
@@ -3323,9 +3334,14 @@ var StatusView = ({ cleanup }) => {
3323
3334
  try {
3324
3335
  const entries = readdirSync(logsDir);
3325
3336
  for (const entry of entries) {
3326
- const logPath = join13(logsDir, entry);
3337
+ const logPath = join12(logsDir, entry);
3327
3338
  if (!isInsideDir(logPath, logsDir)) throw new Error(`Refusing to delete file outside logs directory: ${logPath}`);
3328
- unlinkSync2(logPath);
3339
+ try {
3340
+ if (statSync(logPath).isFile()) {
3341
+ unlinkSync2(logPath);
3342
+ }
3343
+ } catch {
3344
+ }
3329
3345
  }
3330
3346
  } catch {
3331
3347
  }
@@ -3558,8 +3574,8 @@ function registerStatusCommand(program2) {
3558
3574
  }
3559
3575
 
3560
3576
  // src/commands/Wizard.tsx
3561
- import { copyFile as copyFile2, readFile as readFile5, rename, unlink as unlink2, writeFile as writeFile5 } from "fs/promises";
3562
- import { join as join15 } from "path";
3577
+ import { copyFile as copyFile2, readFile as readFile5, rename, unlink, writeFile as writeFile4 } from "fs/promises";
3578
+ import { join as join14 } from "path";
3563
3579
  import { Box as Box11, Text as Text13, useApp as useApp8 } from "ink";
3564
3580
  import { render as render9 } from "ink";
3565
3581
  import Spinner3 from "ink-spinner";
@@ -3605,7 +3621,7 @@ ${variables.defaultConfig}
3605
3621
 
3606
3622
  // src/utils/file-scanner.ts
3607
3623
  import { stat as stat3 } from "fs/promises";
3608
- import { join as join14 } from "path";
3624
+ import { join as join13 } from "path";
3609
3625
  import glob from "fast-glob";
3610
3626
  var FILE_CATEGORIES = {
3611
3627
  shell: {
@@ -3684,7 +3700,7 @@ async function scanHomeDirectory(options) {
3684
3700
  const validFiles = [];
3685
3701
  for (const file of files) {
3686
3702
  try {
3687
- const fullPath = join14(homeDir, file);
3703
+ const fullPath = join13(homeDir, file);
3688
3704
  await stat3(fullPath);
3689
3705
  validFiles.push(file);
3690
3706
  categorizedFiles.add(file);
@@ -3719,7 +3735,7 @@ async function scanHomeDirectory(options) {
3719
3735
  if (categorizedFiles.has(file)) continue;
3720
3736
  if (totalFiles >= maxFiles) break;
3721
3737
  try {
3722
- const fullPath = join14(homeDir, file);
3738
+ const fullPath = join13(homeDir, file);
3723
3739
  await stat3(fullPath);
3724
3740
  uncategorizedFiles.push(file);
3725
3741
  totalFiles++;
@@ -3730,7 +3746,7 @@ async function scanHomeDirectory(options) {
3730
3746
  if (uncategorizedFiles.length > 0) {
3731
3747
  categories.push({
3732
3748
  category: "Other Files",
3733
- files: uncategorizedFiles.sort().slice(0, maxFiles - totalFiles)
3749
+ files: uncategorizedFiles.sort()
3734
3750
  });
3735
3751
  }
3736
3752
  } catch {
@@ -3804,14 +3820,12 @@ var WizardView = ({ printMode }) => {
3804
3820
  useEffect8(() => {
3805
3821
  (async () => {
3806
3822
  try {
3807
- const configPath = join15(getAppDir(), CONFIG_FILENAME);
3823
+ const configPath = join14(getAppDir(), CONFIG_FILENAME);
3808
3824
  if (await fileExists(configPath)) {
3809
- setMessage(
3810
- `Config already exists: ${configPath}
3811
- Would you like to backup and overwrite? (Backup will be saved as config.yml.bak)`
3812
- );
3813
- await rename(configPath, `${configPath}.bak`);
3814
- setMessage(`Backed up existing config to config.yml.bak`);
3825
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3826
+ const bakPath = `${configPath}.${timestamp2}.bak`;
3827
+ await rename(configPath, bakPath);
3828
+ setMessage(`Backed up existing config to ${bakPath}`);
3815
3829
  }
3816
3830
  setPhase("scanning");
3817
3831
  setMessage("Scanning home directory for backup targets...");
@@ -3873,12 +3887,12 @@ Would you like to backup and overwrite? (Backup will be saved as config.yml.bak)
3873
3887
  setPhase("writing");
3874
3888
  setMessage("Writing config.yml...");
3875
3889
  const tmpPath = `${configPath}.tmp`;
3876
- await writeFile5(tmpPath, yamlContent, "utf-8");
3890
+ await writeFile4(tmpPath, yamlContent, "utf-8");
3877
3891
  const verification = validateConfig(parseYAML(yamlContent));
3878
3892
  if (verification.valid) {
3879
3893
  await rename(tmpPath, configPath);
3880
3894
  } else {
3881
- await unlink2(tmpPath);
3895
+ await unlink(tmpPath);
3882
3896
  throw new Error("Final validation failed");
3883
3897
  }
3884
3898
  setPhase("done");
@@ -3967,11 +3981,13 @@ function registerWizardCommand(program2) {
3967
3981
  await waitUntilExit();
3968
3982
  return;
3969
3983
  }
3970
- const configPath = join15(getAppDir(), CONFIG_FILENAME);
3984
+ const configPath = join14(getAppDir(), CONFIG_FILENAME);
3971
3985
  try {
3972
3986
  if (await fileExists(configPath)) {
3973
- console.log(`\u{1F4CB} Backing up existing config to ${configPath}.bak`);
3974
- await rename(configPath, `${configPath}.bak`);
3987
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3988
+ const bakPath = `${configPath}.${timestamp2}.bak`;
3989
+ console.log(`\u{1F4CB} Backing up existing config to ${bakPath}`);
3990
+ await rename(configPath, bakPath);
3975
3991
  }
3976
3992
  if (!await isClaudeCodeAvailable()) {
3977
3993
  throw new Error(
@@ -5,7 +5,6 @@ export declare const METADATA_FILENAME = "_metadata.json";
5
5
  export declare const LARGE_FILE_THRESHOLD: number;
6
6
  export declare const MAX_RETRY = 3;
7
7
  export declare const SENSITIVE_PATTERNS: string[];
8
- export declare const REMOTE_SCRIPT_PATTERN: RegExp;
9
8
  export declare const BACKUPS_DIR = "backups";
10
9
  export declare const TEMPLATES_DIR = "templates";
11
10
  export declare const SCRIPTS_DIR = "scripts";
package/dist/index.cjs CHANGED
@@ -429,7 +429,7 @@ function generateFilename(pattern, options) {
429
429
  var import_promises3 = require("fs/promises");
430
430
  var import_node_path5 = require("path");
431
431
  var import_picocolors = __toESM(require("picocolors"), 1);
432
- var ANSI_RE = /\x1b\[[0-9;]*m/g;
432
+ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
433
433
  function stripAnsi(str) {
434
434
  return str.replace(ANSI_RE, "");
435
435
  }
@@ -560,7 +560,7 @@ function validateMetadata(data) {
560
560
  }
561
561
 
562
562
  // src/version.ts
563
- var VERSION = "0.0.3";
563
+ var VERSION = "0.0.4";
564
564
 
565
565
  // src/core/metadata.ts
566
566
  var METADATA_VERSION = "1.0.0";
@@ -611,7 +611,8 @@ async function collectFileInfo(absolutePath, logicalPath) {
611
611
  }
612
612
  let hash;
613
613
  if (lstats.isSymbolicLink()) {
614
- hash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(absolutePath).digest("hex")}`;
614
+ const linkTarget = await (0, import_promises4.readlink)(absolutePath);
615
+ hash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(linkTarget).digest("hex")}`;
615
616
  } else {
616
617
  hash = await computeFileHash(absolutePath);
617
618
  }
@@ -672,7 +673,8 @@ async function extractArchive(archivePath, destDir) {
672
673
  const normalizedPath = (0, import_node_path6.normalize)(path);
673
674
  if (normalizedPath.includes("..")) return false;
674
675
  if (normalizedPath.startsWith("/")) return false;
675
- if (entry.type === "SymbolicLink" || entry.type === "Link") return false;
676
+ if ("type" in entry && (entry.type === "SymbolicLink" || entry.type === "Link"))
677
+ return false;
676
678
  return true;
677
679
  }
678
680
  });
@@ -857,7 +859,7 @@ async function createBackup(config, options = {}) {
857
859
  }
858
860
  }
859
861
  let allFiles = [...found];
860
- if (config.scripts.includeInBackup) {
862
+ if (config.scripts?.includeInBackup) {
861
863
  const scripts = await collectScripts();
862
864
  allFiles = [...allFiles, ...scripts];
863
865
  }
@@ -1209,8 +1211,10 @@ async function executeStep(step) {
1209
1211
  output: output || void 0
1210
1212
  };
1211
1213
  } catch (err) {
1212
- const error = err;
1213
- const errorOutput = [error.stdout, error.stderr, error.message].filter(Boolean).join("\n").trim();
1214
+ const error = err instanceof Error ? err : new Error(String(err));
1215
+ const stdout = err?.stdout ?? "";
1216
+ const stderr = err?.stderr ?? "";
1217
+ const errorOutput = [stdout, stderr, error.message].filter(Boolean).join("\n").trim();
1214
1218
  return {
1215
1219
  name: step.name,
1216
1220
  status: "failed",
package/dist/index.mjs CHANGED
@@ -379,7 +379,7 @@ function generateFilename(pattern, options) {
379
379
  import { appendFile, mkdir as mkdir2 } from "fs/promises";
380
380
  import { join as join5 } from "path";
381
381
  import pc from "picocolors";
382
- var ANSI_RE = /\x1b\[[0-9;]*m/g;
382
+ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
383
383
  function stripAnsi(str) {
384
384
  return str.replace(ANSI_RE, "");
385
385
  }
@@ -433,7 +433,7 @@ var logger = {
433
433
 
434
434
  // src/core/metadata.ts
435
435
  import { createHash } from "crypto";
436
- import { lstat, readFile as readFile2 } from "fs/promises";
436
+ import { lstat, readFile as readFile2, readlink } from "fs/promises";
437
437
 
438
438
  // src/schemas/metadata.schema.ts
439
439
  var metadataSchema = {
@@ -510,7 +510,7 @@ function validateMetadata(data) {
510
510
  }
511
511
 
512
512
  // src/version.ts
513
- var VERSION = "0.0.3";
513
+ var VERSION = "0.0.4";
514
514
 
515
515
  // src/core/metadata.ts
516
516
  var METADATA_VERSION = "1.0.0";
@@ -561,7 +561,8 @@ async function collectFileInfo(absolutePath, logicalPath) {
561
561
  }
562
562
  let hash;
563
563
  if (lstats.isSymbolicLink()) {
564
- hash = `sha256:${createHash("sha256").update(absolutePath).digest("hex")}`;
564
+ const linkTarget = await readlink(absolutePath);
565
+ hash = `sha256:${createHash("sha256").update(linkTarget).digest("hex")}`;
565
566
  } else {
566
567
  hash = await computeFileHash(absolutePath);
567
568
  }
@@ -622,7 +623,8 @@ async function extractArchive(archivePath, destDir) {
622
623
  const normalizedPath = normalize2(path);
623
624
  if (normalizedPath.includes("..")) return false;
624
625
  if (normalizedPath.startsWith("/")) return false;
625
- if (entry.type === "SymbolicLink" || entry.type === "Link") return false;
626
+ if ("type" in entry && (entry.type === "SymbolicLink" || entry.type === "Link"))
627
+ return false;
626
628
  return true;
627
629
  }
628
630
  });
@@ -807,7 +809,7 @@ async function createBackup(config, options = {}) {
807
809
  }
808
810
  }
809
811
  let allFiles = [...found];
810
- if (config.scripts.includeInBackup) {
812
+ if (config.scripts?.includeInBackup) {
811
813
  const scripts = await collectScripts();
812
814
  allFiles = [...allFiles, ...scripts];
813
815
  }
@@ -1159,8 +1161,10 @@ async function executeStep(step) {
1159
1161
  output: output || void 0
1160
1162
  };
1161
1163
  } catch (err) {
1162
- const error = err;
1163
- const errorOutput = [error.stdout, error.stderr, error.message].filter(Boolean).join("\n").trim();
1164
+ const error = err instanceof Error ? err : new Error(String(err));
1165
+ const stdout = err?.stdout ?? "";
1166
+ const stderr = err?.stderr ?? "";
1167
+ const errorOutput = [stdout, stderr, error.message].filter(Boolean).join("\n").trim();
1164
1168
  return {
1165
1169
  name: step.name,
1166
1170
  status: "failed",
@@ -38,7 +38,7 @@ export interface FileEntry {
38
38
  }
39
39
  export interface TemplateConfig {
40
40
  name: string;
41
- description: string;
41
+ description?: string;
42
42
  backup?: string;
43
43
  sudo?: boolean;
44
44
  steps: TemplateStep[];
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Current package version from package.json
3
3
  * Automatically synchronized during build process
4
4
  */
5
- export declare const VERSION = "0.0.3";
5
+ export declare const VERSION = "0.0.4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumy-pack/syncpoint",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "CLI tool for project synchronization and scaffolding",
5
5
  "keywords": [
6
6
  "cli",
@@ -55,23 +55,22 @@
55
55
  "@types/micromatch": "^4.0.9",
56
56
  "@types/node": "^20.11.0",
57
57
  "@types/react": "^18.0.0",
58
- "@types/tar": "^6.1.13",
59
58
  "@vitest/coverage-v8": "^3.2.4",
60
59
  "ink-testing-library": "^4.0.0"
61
60
  },
62
61
  "scripts": {
63
62
  "build": "node scripts/inject-version.js && tsup && pnpm build:types",
64
- "build:types": "tsc -p ./tsconfig.declarations.json",
65
63
  "build:publish:npm": "pnpm build && pnpm publish:npm",
64
+ "build:types": "tsc -p ./tsconfig.declarations.json",
66
65
  "dev": "node scripts/inject-version.js && tsx src/cli.ts",
67
66
  "format": "prettier --write \"src/**/*.ts\"",
68
67
  "lint": "eslint \"src/**/*.ts\"",
69
68
  "publish:npm": "pnpm publish --access public --no-git-checks",
70
69
  "test": "vitest",
71
- "test:run": "vitest run",
72
- "test:e2e": "vitest run --config vitest.e2e.config.ts src/__tests__/e2e/",
73
- "test:docker": "vitest run --config vitest.e2e.config.ts src/__tests__/docker/",
74
70
  "test:all": "vitest run && pnpm test:e2e && pnpm test:docker",
71
+ "test:docker": "vitest run --config vitest.e2e.config.ts src/__tests__/docker/",
72
+ "test:e2e": "vitest run --config vitest.e2e.config.ts src/__tests__/e2e/",
73
+ "test:run": "vitest run",
75
74
  "version:major": "pnpm version major",
76
75
  "version:minor": "pnpm version minor",
77
76
  "version:patch": "pnpm version patch"