@lumy-pack/syncpoint 0.0.2 → 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/README.md CHANGED
@@ -313,12 +313,12 @@ npx @lumy-pack/syncpoint restore --dry-run
313
313
 
314
314
  ---
315
315
 
316
- ### `syncpoint provision <template> [options]`
316
+ ### `syncpoint provision [template] [options]`
317
317
 
318
318
  Run template-based machine provisioning to install software and configure your system.
319
319
 
320
320
  **What it does:**
321
- 1. Loads template YAML from `~/.syncpoint/templates/`
321
+ 1. Loads template YAML from `~/.syncpoint/templates/` (by name) or from a custom path (with `--file`)
322
322
  2. Validates template structure and security
323
323
  3. Checks for sudo requirement (prompts if needed)
324
324
  4. Executes steps sequentially with real-time progress
@@ -330,22 +330,38 @@ Run template-based machine provisioning to install software and configure your s
330
330
 
331
331
  | Option | Description |
332
332
  |--------|-------------|
333
+ | `-f, --file <path>` | Path to template file (alternative to template name) |
333
334
  | `--dry-run` | Show execution plan without running commands |
334
335
  | `--skip-restore` | Skip automatic config restore after provisioning |
335
336
 
336
337
  **Usage:**
337
338
 
338
339
  ```bash
339
- # Run provisioning template
340
+ # Run provisioning template by name
340
341
  npx @lumy-pack/syncpoint provision my-setup
341
342
 
343
+ # Run template from custom path
344
+ npx @lumy-pack/syncpoint provision --file ./my-template.yml
345
+
346
+ # Use short flag with relative path
347
+ npx @lumy-pack/syncpoint provision -f ~/templates/custom.yaml
348
+
342
349
  # Preview template execution
343
350
  npx @lumy-pack/syncpoint provision my-setup --dry-run
344
351
 
345
352
  # Provision without restoring configs
346
353
  npx @lumy-pack/syncpoint provision my-setup --skip-restore
354
+
355
+ # Combine --file with other options
356
+ npx @lumy-pack/syncpoint provision -f ./template.yml --dry-run --skip-restore
347
357
  ```
348
358
 
359
+ **Path Resolution:**
360
+ - Supports absolute paths: `/path/to/template.yml`
361
+ - Supports relative paths: `./template.yml`, `../templates/setup.yaml`
362
+ - Supports tilde expansion: `~/templates/custom.yml`
363
+ - Must have `.yml` or `.yaml` extension
364
+
349
365
  **Security:**
350
366
  - Blocks dangerous remote script patterns (`curl | bash`, `wget | sh`)
351
367
  - Sanitizes error output to mask sensitive paths and credentials
@@ -616,14 +632,20 @@ steps:
616
632
  ### Running Templates
617
633
 
618
634
  ```bash
635
+ # Run template by name (from ~/.syncpoint/templates/)
636
+ npx @lumy-pack/syncpoint provision dev-setup
637
+
638
+ # Run template from custom path
639
+ npx @lumy-pack/syncpoint provision --file ./my-template.yml
640
+
619
641
  # Preview template execution
620
642
  npx @lumy-pack/syncpoint provision dev-setup --dry-run
621
643
 
622
- # Run template
623
- npx @lumy-pack/syncpoint provision dev-setup
624
-
625
644
  # Run and skip config restore
626
645
  npx @lumy-pack/syncpoint provision dev-setup --skip-restore
646
+
647
+ # Combine custom path with options
648
+ npx @lumy-pack/syncpoint provision -f ~/templates/setup.yaml --dry-run
627
649
  ```
628
650
 
629
651
  ---
@@ -25,6 +25,10 @@ backup:
25
25
  exclude:
26
26
  - "**/*.swp"
27
27
  - "**/.DS_Store"
28
+ - "**/.Trash/**"
29
+ - "**/Library/**"
30
+ - "**/.cache/**"
31
+ - "**/node_modules/**"
28
32
  # Example regex: "/\\.bak$/" (excludes all .bak files)
29
33
 
30
34
  # (Required) Backup archive filename pattern.
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.2";
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
  });
@@ -594,8 +596,18 @@ async function scanTargets(config) {
594
596
  dot: true,
595
597
  absolute: true,
596
598
  onlyFiles: true,
597
- deep: 5
599
+ deep: 5,
598
600
  // Limit depth for performance
601
+ suppressErrors: true,
602
+ ignore: [
603
+ "**/.Trash/**",
604
+ "**/Library/**",
605
+ "**/.cache/**",
606
+ "**/node_modules/**",
607
+ ...config.backup.exclude.filter(
608
+ (p) => detectPatternType(p) === "glob"
609
+ )
610
+ ]
599
611
  });
600
612
  for (const match of allFiles) {
601
613
  if (regex.test(match) && !isExcluded(match)) {
@@ -615,8 +627,15 @@ async function scanTargets(config) {
615
627
  const matches = await fg(expanded, {
616
628
  dot: true,
617
629
  absolute: true,
618
- ignore: globExcludes,
619
- onlyFiles: true
630
+ ignore: [
631
+ "**/.Trash/**",
632
+ "**/Library/**",
633
+ "**/.cache/**",
634
+ "**/node_modules/**",
635
+ ...globExcludes
636
+ ],
637
+ onlyFiles: true,
638
+ suppressErrors: true
620
639
  });
621
640
  for (const match of matches) {
622
641
  if (!isExcluded(match)) {
@@ -640,9 +659,16 @@ async function scanTargets(config) {
640
659
  const matches = await fg(dirGlob, {
641
660
  dot: true,
642
661
  absolute: true,
643
- ignore: globExcludes,
644
- onlyFiles: true
662
+ ignore: [
663
+ "**/.Trash/**",
664
+ "**/Library/**",
665
+ "**/.cache/**",
666
+ "**/node_modules/**",
667
+ ...globExcludes
668
+ ],
669
+ onlyFiles: true,
645
670
  // Only include files, not subdirectories
671
+ suppressErrors: true
646
672
  });
647
673
  for (const match of matches) {
648
674
  if (!isExcluded(match)) {
@@ -699,7 +725,7 @@ async function createBackup(config, options = {}) {
699
725
  }
700
726
  }
701
727
  let allFiles = [...found];
702
- if (config.scripts.includeInBackup) {
728
+ if (config.scripts?.includeInBackup) {
703
729
  const scripts = await collectScripts();
704
730
  allFiles = [...allFiles, ...scripts];
705
731
  }
@@ -947,10 +973,14 @@ var COMMANDS = {
947
973
  {
948
974
  name: "template",
949
975
  description: "Template name to execute",
950
- required: true
976
+ required: false
951
977
  }
952
978
  ],
953
979
  options: [
980
+ {
981
+ flag: "-f, --file <path>",
982
+ description: "Path to template file (alternative to template name)"
983
+ },
954
984
  {
955
985
  flag: "--dry-run",
956
986
  description: "Show execution plan without running commands"
@@ -963,7 +993,9 @@ var COMMANDS = {
963
993
  examples: [
964
994
  "npx @lumy-pack/syncpoint provision dev-setup",
965
995
  "npx @lumy-pack/syncpoint provision dev-setup --dry-run",
966
- "npx @lumy-pack/syncpoint provision dev-setup --skip-restore"
996
+ "npx @lumy-pack/syncpoint provision dev-setup --skip-restore",
997
+ "npx @lumy-pack/syncpoint provision --file ./my-template.yml",
998
+ "npx @lumy-pack/syncpoint provision -f ~/templates/custom.yaml --dry-run"
967
999
  ]
968
1000
  },
969
1001
  "create-template": {
@@ -1051,6 +1083,7 @@ var BackupView = ({ options }) => {
1051
1083
  const [error, setError] = useState(null);
1052
1084
  useEffect(() => {
1053
1085
  (async () => {
1086
+ let progressInterval;
1054
1087
  try {
1055
1088
  const cfg = await loadConfig();
1056
1089
  setConfig(cfg);
@@ -1063,7 +1096,7 @@ var BackupView = ({ options }) => {
1063
1096
  return;
1064
1097
  }
1065
1098
  setPhase("compressing");
1066
- const progressInterval = setInterval(() => {
1099
+ progressInterval = setInterval(() => {
1067
1100
  setProgress((prev) => {
1068
1101
  if (prev >= 90) return prev;
1069
1102
  return prev + 10;
@@ -1076,9 +1109,10 @@ var BackupView = ({ options }) => {
1076
1109
  setPhase("done");
1077
1110
  setTimeout(() => exit(), 100);
1078
1111
  } catch (err) {
1112
+ if (progressInterval) clearInterval(progressInterval);
1079
1113
  setError(err instanceof Error ? err.message : String(err));
1080
1114
  setPhase("error");
1081
- exit();
1115
+ setTimeout(() => exit(), 100);
1082
1116
  }
1083
1117
  })();
1084
1118
  }, []);
@@ -1132,7 +1166,7 @@ var BackupView = ({ options }) => {
1132
1166
  /* @__PURE__ */ jsxs2(Text2, { children: [
1133
1167
  " ",
1134
1168
  "File: ",
1135
- result.metadata.config.filename
1169
+ result.archivePath.split("/").pop()
1136
1170
  ] }),
1137
1171
  /* @__PURE__ */ jsxs2(Text2, { children: [
1138
1172
  " ",
@@ -1169,8 +1203,8 @@ import { useState as useState2, useEffect as useEffect2 } from "react";
1169
1203
  import { Text as Text3, Box as Box2, useApp as useApp2 } from "ink";
1170
1204
  import Spinner from "ink-spinner";
1171
1205
  import { render as render2 } from "ink";
1172
- import { join as join9 } from "path";
1173
- import { writeFile as writeFile4 } from "fs/promises";
1206
+ import { join as join8 } from "path";
1207
+ import { writeFile as writeFile3 } from "fs/promises";
1174
1208
 
1175
1209
  // src/schemas/template.schema.ts
1176
1210
  var templateSchema = {
@@ -1252,9 +1286,6 @@ Begin by asking the user to describe their provisioning needs.`;
1252
1286
 
1253
1287
  // src/utils/claude-code-runner.ts
1254
1288
  import { spawn } from "child_process";
1255
- import { unlink, writeFile as writeFile3 } from "fs/promises";
1256
- import { tmpdir as tmpdir2 } from "os";
1257
- import { join as join8 } from "path";
1258
1289
  async function isClaudeCodeAvailable() {
1259
1290
  return new Promise((resolve2) => {
1260
1291
  const child = spawn("which", ["claude"], { shell: true });
@@ -1268,58 +1299,49 @@ async function isClaudeCodeAvailable() {
1268
1299
  }
1269
1300
  async function invokeClaudeCode(prompt, options) {
1270
1301
  const timeout = options?.timeout ?? 12e4;
1271
- const promptFile = join8(tmpdir2(), `syncpoint-prompt-${Date.now()}.txt`);
1272
- await writeFile3(promptFile, prompt, "utf-8");
1273
- try {
1274
- return await new Promise((resolve2, reject) => {
1275
- const args = ["--permission-mode", "acceptEdits", "--model", "sonnet"];
1276
- if (options?.sessionId) {
1277
- 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
+ });
1278
1338
  }
1279
- const child = spawn("claude", args, {
1280
- stdio: ["pipe", "pipe", "pipe"]
1281
- });
1282
- let stdout = "";
1283
- let stderr = "";
1284
- child.stdout.on("data", (data) => {
1285
- stdout += data.toString();
1286
- });
1287
- child.stderr.on("data", (data) => {
1288
- stderr += data.toString();
1289
- });
1290
- child.stdin.write(prompt);
1291
- child.stdin.end();
1292
- const timer = setTimeout(() => {
1293
- child.kill();
1294
- reject(new Error(`Claude Code invocation timeout after ${timeout}ms`));
1295
- }, timeout);
1296
- child.on("close", (code) => {
1297
- clearTimeout(timer);
1298
- if (code === 0) {
1299
- resolve2({
1300
- success: true,
1301
- output: stdout,
1302
- sessionId: options?.sessionId
1303
- });
1304
- } else {
1305
- resolve2({
1306
- success: false,
1307
- output: stdout,
1308
- error: stderr || `Process exited with code ${code}`
1309
- });
1310
- }
1311
- });
1312
- child.on("error", (err) => {
1313
- clearTimeout(timer);
1314
- reject(err);
1315
- });
1316
1339
  });
1317
- } finally {
1318
- try {
1319
- await unlink(promptFile);
1320
- } catch {
1321
- }
1322
- }
1340
+ child.on("error", (err) => {
1341
+ clearTimeout(timer);
1342
+ reject(err);
1343
+ });
1344
+ });
1323
1345
  }
1324
1346
  async function resumeClaudeCodeSession(sessionId, prompt, options) {
1325
1347
  return invokeClaudeCode(prompt, {
@@ -1503,14 +1525,14 @@ var CreateTemplateView = ({
1503
1525
  setPhase("writing");
1504
1526
  setMessage("Writing template...");
1505
1527
  const filename = templateName ? `${templateName}.yml` : `${parsedTemplate.name.toLowerCase().replace(/\s+/g, "-")}.yml`;
1506
- const templatePath = join9(templatesDir, filename);
1528
+ const templatePath = join8(templatesDir, filename);
1507
1529
  if (await fileExists(templatePath)) {
1508
1530
  throw new Error(
1509
1531
  `Template already exists: ${filename}
1510
1532
  Please choose a different name or delete the existing template.`
1511
1533
  );
1512
1534
  }
1513
- await writeFile4(templatePath, yamlContent, "utf-8");
1535
+ await writeFile3(templatePath, yamlContent, "utf-8");
1514
1536
  setPhase("done");
1515
1537
  setMessage(`\u2713 Template created: ${filename}`);
1516
1538
  setTimeout(() => exit(), 100);
@@ -1623,7 +1645,7 @@ var GeneralHelpView = () => {
1623
1645
  "Restore configuration files"
1624
1646
  ] }),
1625
1647
  /* @__PURE__ */ jsxs4(Text4, { children: [
1626
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "provision <template>" }),
1648
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "provision [template]" }),
1627
1649
  " ",
1628
1650
  "Run machine provisioning template"
1629
1651
  ] }),
@@ -1736,7 +1758,7 @@ function registerHelpCommand(program2) {
1736
1758
  import { useState as useState3, useEffect as useEffect3 } from "react";
1737
1759
  import { Text as Text5, Box as Box4, useApp as useApp3 } from "ink";
1738
1760
  import { render as render4 } from "ink";
1739
- import { join as join10 } from "path";
1761
+ import { join as join9 } from "path";
1740
1762
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1741
1763
  var InitView = () => {
1742
1764
  const { exit } = useApp3();
@@ -1747,7 +1769,7 @@ var InitView = () => {
1747
1769
  (async () => {
1748
1770
  try {
1749
1771
  const appDir = getAppDir();
1750
- if (await fileExists(join10(appDir, CONFIG_FILENAME))) {
1772
+ if (await fileExists(join9(appDir, CONFIG_FILENAME))) {
1751
1773
  setError(`Already initialized: ${appDir}`);
1752
1774
  exit();
1753
1775
  return;
@@ -1780,11 +1802,11 @@ var InitView = () => {
1780
1802
  await initDefaultConfig();
1781
1803
  completed.push({ name: `Created ${CONFIG_FILENAME} (defaults)`, done: true });
1782
1804
  setSteps([...completed]);
1783
- const exampleTemplatePath = join10(getSubDir(TEMPLATES_DIR), "example.yml");
1805
+ const exampleTemplatePath = join9(getSubDir(TEMPLATES_DIR), "example.yml");
1784
1806
  if (!await fileExists(exampleTemplatePath)) {
1785
- const { writeFile: writeFile6 } = await import("fs/promises");
1807
+ const { writeFile: writeFile5 } = await import("fs/promises");
1786
1808
  const exampleYaml = readAsset("template.example.yml");
1787
- await writeFile6(exampleTemplatePath, exampleYaml, "utf-8");
1809
+ await writeFile5(exampleTemplatePath, exampleYaml, "utf-8");
1788
1810
  completed.push({ name: `Created templates/example.yml`, done: true });
1789
1811
  setSteps([...completed]);
1790
1812
  }
@@ -1916,7 +1938,7 @@ var Table = ({
1916
1938
  // src/core/provision.ts
1917
1939
  import { exec } from "child_process";
1918
1940
  import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
1919
- import { join as join11 } from "path";
1941
+ import { join as join10 } from "path";
1920
1942
  import YAML3 from "yaml";
1921
1943
  var REMOTE_SCRIPT_PATTERNS = [
1922
1944
  /curl\s.*\|\s*(ba)?sh/,
@@ -1956,7 +1978,7 @@ async function listTemplates() {
1956
1978
  if (!entry.isFile() || !entry.name.endsWith(".yml") && !entry.name.endsWith(".yaml")) {
1957
1979
  continue;
1958
1980
  }
1959
- const fullPath = join11(templatesDir, entry.name);
1981
+ const fullPath = join10(templatesDir, entry.name);
1960
1982
  try {
1961
1983
  const config = await loadTemplate(fullPath);
1962
1984
  templates.push({
@@ -2033,8 +2055,10 @@ async function executeStep(step) {
2033
2055
  output: output || void 0
2034
2056
  };
2035
2057
  } catch (err) {
2036
- const error = err;
2037
- 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();
2038
2062
  return {
2039
2063
  name: step.name,
2040
2064
  status: "failed",
@@ -2073,7 +2097,7 @@ async function* runProvision(templatePath, options = {}) {
2073
2097
 
2074
2098
  // src/core/restore.ts
2075
2099
  import { copyFile, lstat as lstat2, readdir as readdir3, stat as stat2 } from "fs/promises";
2076
- import { dirname as dirname2, join as join12 } from "path";
2100
+ import { dirname as dirname2, join as join11 } from "path";
2077
2101
  async function getBackupList(config) {
2078
2102
  const backupDir = config?.backup.destination ? resolveTargetPath(config.backup.destination) : getSubDir(BACKUPS_DIR);
2079
2103
  const exists = await fileExists(backupDir);
@@ -2082,7 +2106,7 @@ async function getBackupList(config) {
2082
2106
  const backups = [];
2083
2107
  for (const entry of entries) {
2084
2108
  if (!entry.isFile() || !entry.name.endsWith(".tar.gz")) continue;
2085
- const fullPath = join12(backupDir, entry.name);
2109
+ const fullPath = join11(backupDir, entry.name);
2086
2110
  const fileStat = await stat2(fullPath);
2087
2111
  let hostname;
2088
2112
  let fileCount;
@@ -2157,7 +2181,7 @@ async function createSafetyBackup(filePaths) {
2157
2181
  const filename = `_pre-restore_${formatDatetime(now)}.tar.gz`;
2158
2182
  const backupDir = getSubDir(BACKUPS_DIR);
2159
2183
  await ensureDir(backupDir);
2160
- const archivePath = join12(backupDir, filename);
2184
+ const archivePath = join11(backupDir, filename);
2161
2185
  const files = [];
2162
2186
  for (const fp of filePaths) {
2163
2187
  const absPath = resolveTargetPath(fp);
@@ -2191,8 +2215,8 @@ async function restoreBackup(archivePath, options = {}) {
2191
2215
  };
2192
2216
  }
2193
2217
  const { mkdtemp: mkdtemp2, rm: rm2 } = await import("fs/promises");
2194
- const { tmpdir: tmpdir3 } = await import("os");
2195
- const tmpDir = await mkdtemp2(join12(tmpdir3(), "syncpoint-restore-"));
2218
+ const { tmpdir: tmpdir2 } = await import("os");
2219
+ const tmpDir = await mkdtemp2(join11(tmpdir2(), "syncpoint-restore-"));
2196
2220
  try {
2197
2221
  await extractArchive(archivePath, tmpDir);
2198
2222
  for (const action of plan.actions) {
@@ -2201,7 +2225,7 @@ async function restoreBackup(archivePath, options = {}) {
2201
2225
  continue;
2202
2226
  }
2203
2227
  const archiveName = action.path.startsWith("~/") ? action.path.slice(2) : action.path;
2204
- const extractedPath = join12(tmpDir, archiveName);
2228
+ const extractedPath = join11(tmpDir, archiveName);
2205
2229
  const destPath = resolveTargetPath(action.path);
2206
2230
  const extractedExists = await fileExists(extractedPath);
2207
2231
  if (!extractedExists) {
@@ -2596,6 +2620,10 @@ var ListView = ({ type, deleteIndex }) => {
2596
2620
  function registerListCommand(program2) {
2597
2621
  program2.command("list [type]").description("List backups and templates").option("--delete <n>", "Delete item #n").action(async (type, opts) => {
2598
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
+ }
2599
2627
  const { waitUntilExit } = render5(
2600
2628
  /* @__PURE__ */ jsx8(ListView, { type, deleteIndex })
2601
2629
  );
@@ -2749,6 +2777,18 @@ var ProvisionView = ({
2749
2777
  stepIdx++;
2750
2778
  }
2751
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
+ }
2752
2792
  setPhase("done");
2753
2793
  setTimeout(() => exit(), 100);
2754
2794
  } catch (err) {
@@ -2852,17 +2892,36 @@ var ProvisionView = ({
2852
2892
  ] });
2853
2893
  };
2854
2894
  function registerProvisionCommand(program2) {
2855
- program2.command("provision <template>").description("Run template-based machine provisioning").option("--dry-run", "Show plan without execution", false).option("--skip-restore", "Skip automatic restore after template completion", false).action(
2895
+ program2.command("provision [template]").description("Run template-based machine provisioning").option("--dry-run", "Show plan without execution", false).option("--skip-restore", "Skip automatic restore after template completion", false).option("-f, --file <path>", "Path to template file").action(
2856
2896
  async (templateName, opts) => {
2857
- const templates = await listTemplates();
2858
- const match = templates.find(
2859
- (t) => t.name === templateName || t.name === `${templateName}.yml` || t.config.name === templateName
2860
- );
2861
- if (!match) {
2862
- console.error(`Template not found: ${templateName}`);
2897
+ let templatePath;
2898
+ if (opts.file) {
2899
+ templatePath = resolveTargetPath(opts.file);
2900
+ if (!await fileExists(templatePath)) {
2901
+ console.error(`Template file not found: ${opts.file}`);
2902
+ process.exit(1);
2903
+ }
2904
+ if (!templatePath.endsWith(".yml") && !templatePath.endsWith(".yaml")) {
2905
+ console.error(`Template file must have .yml or .yaml extension: ${opts.file}`);
2906
+ process.exit(1);
2907
+ }
2908
+ } else if (templateName) {
2909
+ const templates = await listTemplates();
2910
+ const match = templates.find(
2911
+ (t) => t.name === templateName || t.name === `${templateName}.yml` || t.config.name === templateName
2912
+ );
2913
+ if (!match) {
2914
+ console.error(`Template not found: ${templateName}`);
2915
+ process.exit(1);
2916
+ }
2917
+ templatePath = match.path;
2918
+ } else {
2919
+ console.error("Error: Either <template> name or --file option must be provided");
2920
+ console.error("Usage: syncpoint provision <template> [options]");
2921
+ console.error(" syncpoint provision --file <path> [options]");
2863
2922
  process.exit(1);
2864
2923
  }
2865
- const tmpl = await loadTemplate(match.path);
2924
+ const tmpl = await loadTemplate(templatePath);
2866
2925
  if (tmpl.sudo && !opts.dryRun) {
2867
2926
  ensureSudo(tmpl.name);
2868
2927
  }
@@ -2871,7 +2930,7 @@ function registerProvisionCommand(program2) {
2871
2930
  ProvisionView,
2872
2931
  {
2873
2932
  template: tmpl,
2874
- templatePath: match.path,
2933
+ templatePath,
2875
2934
  options: {
2876
2935
  dryRun: opts.dryRun,
2877
2936
  skipRestore: opts.skipRestore
@@ -2902,7 +2961,8 @@ var RestoreView = ({ filename, options }) => {
2902
2961
  useEffect6(() => {
2903
2962
  (async () => {
2904
2963
  try {
2905
- const list2 = await getBackupList();
2964
+ const config = await loadConfig();
2965
+ const list2 = await getBackupList(config);
2906
2966
  setBackups(list2);
2907
2967
  if (list2.length === 0) {
2908
2968
  setError("No backups available.");
@@ -3115,7 +3175,7 @@ function registerRestoreCommand(program2) {
3115
3175
 
3116
3176
  // src/commands/Status.tsx
3117
3177
  import { readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
3118
- import { join as join13 } from "path";
3178
+ import { join as join12 } from "path";
3119
3179
  import { Box as Box10, Text as Text12, useApp as useApp7, useInput as useInput3 } from "ink";
3120
3180
  import { render as render8 } from "ink";
3121
3181
  import SelectInput3 from "ink-select-input";
@@ -3128,7 +3188,7 @@ function getDirStats(dirPath) {
3128
3188
  let count = 0;
3129
3189
  for (const entry of entries) {
3130
3190
  try {
3131
- const stat4 = statSync(join13(dirPath, entry));
3191
+ const stat4 = statSync(join12(dirPath, entry));
3132
3192
  if (stat4.isFile()) {
3133
3193
  totalSize += stat4.size;
3134
3194
  count++;
@@ -3274,9 +3334,14 @@ var StatusView = ({ cleanup }) => {
3274
3334
  try {
3275
3335
  const entries = readdirSync(logsDir);
3276
3336
  for (const entry of entries) {
3277
- const logPath = join13(logsDir, entry);
3337
+ const logPath = join12(logsDir, entry);
3278
3338
  if (!isInsideDir(logPath, logsDir)) throw new Error(`Refusing to delete file outside logs directory: ${logPath}`);
3279
- unlinkSync2(logPath);
3339
+ try {
3340
+ if (statSync(logPath).isFile()) {
3341
+ unlinkSync2(logPath);
3342
+ }
3343
+ } catch {
3344
+ }
3280
3345
  }
3281
3346
  } catch {
3282
3347
  }
@@ -3509,8 +3574,8 @@ function registerStatusCommand(program2) {
3509
3574
  }
3510
3575
 
3511
3576
  // src/commands/Wizard.tsx
3512
- import { copyFile as copyFile2, readFile as readFile5, rename, unlink as unlink2, writeFile as writeFile5 } from "fs/promises";
3513
- 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";
3514
3579
  import { Box as Box11, Text as Text13, useApp as useApp8 } from "ink";
3515
3580
  import { render as render9 } from "ink";
3516
3581
  import Spinner3 from "ink-spinner";
@@ -3556,7 +3621,7 @@ ${variables.defaultConfig}
3556
3621
 
3557
3622
  // src/utils/file-scanner.ts
3558
3623
  import { stat as stat3 } from "fs/promises";
3559
- import { join as join14 } from "path";
3624
+ import { join as join13 } from "path";
3560
3625
  import glob from "fast-glob";
3561
3626
  var FILE_CATEGORIES = {
3562
3627
  shell: {
@@ -3629,12 +3694,13 @@ async function scanHomeDirectory(options) {
3629
3694
  onlyFiles: true,
3630
3695
  deep: maxDepth,
3631
3696
  absolute: false,
3632
- cwd: homeDir
3697
+ cwd: homeDir,
3698
+ suppressErrors: true
3633
3699
  });
3634
3700
  const validFiles = [];
3635
3701
  for (const file of files) {
3636
3702
  try {
3637
- const fullPath = join14(homeDir, file);
3703
+ const fullPath = join13(homeDir, file);
3638
3704
  await stat3(fullPath);
3639
3705
  validFiles.push(file);
3640
3706
  categorizedFiles.add(file);
@@ -3661,14 +3727,15 @@ async function scanHomeDirectory(options) {
3661
3727
  onlyFiles: true,
3662
3728
  deep: maxDepth,
3663
3729
  absolute: false,
3664
- cwd: homeDir
3730
+ cwd: homeDir,
3731
+ suppressErrors: true
3665
3732
  });
3666
3733
  const uncategorizedFiles = [];
3667
3734
  for (const file of allFiles) {
3668
3735
  if (categorizedFiles.has(file)) continue;
3669
3736
  if (totalFiles >= maxFiles) break;
3670
3737
  try {
3671
- const fullPath = join14(homeDir, file);
3738
+ const fullPath = join13(homeDir, file);
3672
3739
  await stat3(fullPath);
3673
3740
  uncategorizedFiles.push(file);
3674
3741
  totalFiles++;
@@ -3679,7 +3746,7 @@ async function scanHomeDirectory(options) {
3679
3746
  if (uncategorizedFiles.length > 0) {
3680
3747
  categories.push({
3681
3748
  category: "Other Files",
3682
- files: uncategorizedFiles.sort().slice(0, maxFiles - totalFiles)
3749
+ files: uncategorizedFiles.sort()
3683
3750
  });
3684
3751
  }
3685
3752
  } catch {
@@ -3753,14 +3820,12 @@ var WizardView = ({ printMode }) => {
3753
3820
  useEffect8(() => {
3754
3821
  (async () => {
3755
3822
  try {
3756
- const configPath = join15(getAppDir(), CONFIG_FILENAME);
3823
+ const configPath = join14(getAppDir(), CONFIG_FILENAME);
3757
3824
  if (await fileExists(configPath)) {
3758
- setMessage(
3759
- `Config already exists: ${configPath}
3760
- Would you like to backup and overwrite? (Backup will be saved as config.yml.bak)`
3761
- );
3762
- await rename(configPath, `${configPath}.bak`);
3763
- 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}`);
3764
3829
  }
3765
3830
  setPhase("scanning");
3766
3831
  setMessage("Scanning home directory for backup targets...");
@@ -3822,12 +3887,12 @@ Would you like to backup and overwrite? (Backup will be saved as config.yml.bak)
3822
3887
  setPhase("writing");
3823
3888
  setMessage("Writing config.yml...");
3824
3889
  const tmpPath = `${configPath}.tmp`;
3825
- await writeFile5(tmpPath, yamlContent, "utf-8");
3890
+ await writeFile4(tmpPath, yamlContent, "utf-8");
3826
3891
  const verification = validateConfig(parseYAML(yamlContent));
3827
3892
  if (verification.valid) {
3828
3893
  await rename(tmpPath, configPath);
3829
3894
  } else {
3830
- await unlink2(tmpPath);
3895
+ await unlink(tmpPath);
3831
3896
  throw new Error("Final validation failed");
3832
3897
  }
3833
3898
  setPhase("done");
@@ -3916,11 +3981,13 @@ function registerWizardCommand(program2) {
3916
3981
  await waitUntilExit();
3917
3982
  return;
3918
3983
  }
3919
- const configPath = join15(getAppDir(), CONFIG_FILENAME);
3984
+ const configPath = join14(getAppDir(), CONFIG_FILENAME);
3920
3985
  try {
3921
3986
  if (await fileExists(configPath)) {
3922
- console.log(`\u{1F4CB} Backing up existing config to ${configPath}.bak`);
3923
- 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);
3924
3991
  }
3925
3992
  if (!await isClaudeCodeAvailable()) {
3926
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.2";
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
  });
@@ -728,8 +730,18 @@ async function scanTargets(config) {
728
730
  dot: true,
729
731
  absolute: true,
730
732
  onlyFiles: true,
731
- deep: 5
733
+ deep: 5,
732
734
  // Limit depth for performance
735
+ suppressErrors: true,
736
+ ignore: [
737
+ "**/.Trash/**",
738
+ "**/Library/**",
739
+ "**/.cache/**",
740
+ "**/node_modules/**",
741
+ ...config.backup.exclude.filter(
742
+ (p) => detectPatternType(p) === "glob"
743
+ )
744
+ ]
733
745
  });
734
746
  for (const match of allFiles) {
735
747
  if (regex.test(match) && !isExcluded(match)) {
@@ -749,8 +761,15 @@ async function scanTargets(config) {
749
761
  const matches = await (0, import_fast_glob.default)(expanded, {
750
762
  dot: true,
751
763
  absolute: true,
752
- ignore: globExcludes,
753
- onlyFiles: true
764
+ ignore: [
765
+ "**/.Trash/**",
766
+ "**/Library/**",
767
+ "**/.cache/**",
768
+ "**/node_modules/**",
769
+ ...globExcludes
770
+ ],
771
+ onlyFiles: true,
772
+ suppressErrors: true
754
773
  });
755
774
  for (const match of matches) {
756
775
  if (!isExcluded(match)) {
@@ -774,9 +793,16 @@ async function scanTargets(config) {
774
793
  const matches = await (0, import_fast_glob.default)(dirGlob, {
775
794
  dot: true,
776
795
  absolute: true,
777
- ignore: globExcludes,
778
- onlyFiles: true
796
+ ignore: [
797
+ "**/.Trash/**",
798
+ "**/Library/**",
799
+ "**/.cache/**",
800
+ "**/node_modules/**",
801
+ ...globExcludes
802
+ ],
803
+ onlyFiles: true,
779
804
  // Only include files, not subdirectories
805
+ suppressErrors: true
780
806
  });
781
807
  for (const match of matches) {
782
808
  if (!isExcluded(match)) {
@@ -833,7 +859,7 @@ async function createBackup(config, options = {}) {
833
859
  }
834
860
  }
835
861
  let allFiles = [...found];
836
- if (config.scripts.includeInBackup) {
862
+ if (config.scripts?.includeInBackup) {
837
863
  const scripts = await collectScripts();
838
864
  allFiles = [...allFiles, ...scripts];
839
865
  }
@@ -1185,8 +1211,10 @@ async function executeStep(step) {
1185
1211
  output: output || void 0
1186
1212
  };
1187
1213
  } catch (err) {
1188
- const error = err;
1189
- 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();
1190
1218
  return {
1191
1219
  name: step.name,
1192
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.2";
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
  });
@@ -678,8 +680,18 @@ async function scanTargets(config) {
678
680
  dot: true,
679
681
  absolute: true,
680
682
  onlyFiles: true,
681
- deep: 5
683
+ deep: 5,
682
684
  // Limit depth for performance
685
+ suppressErrors: true,
686
+ ignore: [
687
+ "**/.Trash/**",
688
+ "**/Library/**",
689
+ "**/.cache/**",
690
+ "**/node_modules/**",
691
+ ...config.backup.exclude.filter(
692
+ (p) => detectPatternType(p) === "glob"
693
+ )
694
+ ]
683
695
  });
684
696
  for (const match of allFiles) {
685
697
  if (regex.test(match) && !isExcluded(match)) {
@@ -699,8 +711,15 @@ async function scanTargets(config) {
699
711
  const matches = await fg(expanded, {
700
712
  dot: true,
701
713
  absolute: true,
702
- ignore: globExcludes,
703
- onlyFiles: true
714
+ ignore: [
715
+ "**/.Trash/**",
716
+ "**/Library/**",
717
+ "**/.cache/**",
718
+ "**/node_modules/**",
719
+ ...globExcludes
720
+ ],
721
+ onlyFiles: true,
722
+ suppressErrors: true
704
723
  });
705
724
  for (const match of matches) {
706
725
  if (!isExcluded(match)) {
@@ -724,9 +743,16 @@ async function scanTargets(config) {
724
743
  const matches = await fg(dirGlob, {
725
744
  dot: true,
726
745
  absolute: true,
727
- ignore: globExcludes,
728
- onlyFiles: true
746
+ ignore: [
747
+ "**/.Trash/**",
748
+ "**/Library/**",
749
+ "**/.cache/**",
750
+ "**/node_modules/**",
751
+ ...globExcludes
752
+ ],
753
+ onlyFiles: true,
729
754
  // Only include files, not subdirectories
755
+ suppressErrors: true
730
756
  });
731
757
  for (const match of matches) {
732
758
  if (!isExcluded(match)) {
@@ -783,7 +809,7 @@ async function createBackup(config, options = {}) {
783
809
  }
784
810
  }
785
811
  let allFiles = [...found];
786
- if (config.scripts.includeInBackup) {
812
+ if (config.scripts?.includeInBackup) {
787
813
  const scripts = await collectScripts();
788
814
  allFiles = [...allFiles, ...scripts];
789
815
  }
@@ -1135,8 +1161,10 @@ async function executeStep(step) {
1135
1161
  output: output || void 0
1136
1162
  };
1137
1163
  } catch (err) {
1138
- const error = err;
1139
- 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();
1140
1168
  return {
1141
1169
  name: step.name,
1142
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[];
@@ -92,6 +92,7 @@ export interface RestoreOptions {
92
92
  export interface ProvisionOptions {
93
93
  dryRun?: boolean;
94
94
  skipRestore?: boolean;
95
+ file?: string;
95
96
  }
96
97
  export interface BackupInfo {
97
98
  filename: string;
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.2";
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.2",
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"