@lumy-pack/syncpoint 0.0.3 → 0.0.5

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.
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMigrateCommand(program: Command): void;
@@ -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";
@@ -0,0 +1,11 @@
1
+ import type { MigrateResult } from '../utils/types.js';
2
+ export interface DiffResult {
3
+ added: string[][];
4
+ removed: string[][];
5
+ existing: string[][];
6
+ }
7
+ export declare function diffConfigFields(userData: unknown): DiffResult;
8
+ export declare function buildMigratedDocument(templateText: string, userData: unknown, diff: DiffResult): string;
9
+ export declare function migrateConfig(options?: {
10
+ dryRun?: boolean;
11
+ }): Promise<MigrateResult>;
package/dist/index.cjs CHANGED
@@ -126,6 +126,76 @@ function getSubDir(sub) {
126
126
  return (0, import_node_path2.join)(getAppDir(), sub);
127
127
  }
128
128
 
129
+ // assets/schemas/config.schema.json
130
+ var config_schema_default = {
131
+ $schema: "http://json-schema.org/draft-07/schema#",
132
+ title: "Syncpoint Config",
133
+ description: "Configuration for syncpoint backup tool",
134
+ type: "object",
135
+ required: [
136
+ "backup"
137
+ ],
138
+ properties: {
139
+ backup: {
140
+ type: "object",
141
+ description: "Backup configuration",
142
+ required: [
143
+ "targets",
144
+ "exclude",
145
+ "filename"
146
+ ],
147
+ properties: {
148
+ targets: {
149
+ type: "array",
150
+ description: "List of files/directories to backup. Supports literal paths (e.g. ~/.zshrc), glob patterns (e.g. ~/.config/*.conf), and regex patterns (e.g. /\\.conf$/).",
151
+ items: {
152
+ type: "string",
153
+ validPattern: true
154
+ }
155
+ },
156
+ exclude: {
157
+ type: "array",
158
+ description: "List of patterns to exclude from backup. Supports glob (e.g. **/*.swp) and regex (e.g. /\\.bak$/) patterns.",
159
+ items: {
160
+ type: "string",
161
+ validPattern: true
162
+ }
163
+ },
164
+ filename: {
165
+ type: "string",
166
+ description: "Backup archive filename pattern. Available variables: {hostname}, {datetime}.",
167
+ minLength: 1
168
+ },
169
+ destination: {
170
+ type: "string",
171
+ description: "Backup archive destination path. Default: ~/.syncpoint/backups/"
172
+ },
173
+ includeSensitiveFiles: {
174
+ type: "boolean",
175
+ description: "Include sensitive files (SSH keys, certificates, etc.) in backup. When false (default), files matching sensitive patterns (id_rsa, id_ed25519, *.pem, *.key) are automatically excluded."
176
+ }
177
+ },
178
+ additionalProperties: false
179
+ },
180
+ scripts: {
181
+ type: "object",
182
+ description: "Scripts configuration",
183
+ properties: {
184
+ includeInBackup: {
185
+ type: "boolean",
186
+ description: "Whether to include scripts/ directory in backup. Default: true."
187
+ }
188
+ },
189
+ additionalProperties: false
190
+ },
191
+ "yaml-language-server": {
192
+ type: "string",
193
+ description: "Editor directive for schema association; ignored at runtime."
194
+ }
195
+ },
196
+ additionalProperties: false
197
+ };
198
+
129
199
  // src/schemas/ajv.ts
130
200
  var import_ajv = __toESM(require("ajv"), 1);
131
201
  var import_ajv_formats = __toESM(require("ajv-formats"), 1);
@@ -231,45 +301,7 @@ ajv.addKeyword({
231
301
  });
232
302
 
233
303
  // src/schemas/config.schema.ts
234
- var configSchema = {
235
- type: "object",
236
- required: ["backup"],
237
- properties: {
238
- backup: {
239
- type: "object",
240
- required: ["targets", "exclude", "filename"],
241
- properties: {
242
- targets: {
243
- type: "array",
244
- items: { type: "string", validPattern: true }
245
- },
246
- exclude: {
247
- type: "array",
248
- items: { type: "string", validPattern: true }
249
- },
250
- filename: {
251
- type: "string",
252
- minLength: 1
253
- },
254
- destination: {
255
- type: "string"
256
- }
257
- },
258
- additionalProperties: false
259
- },
260
- scripts: {
261
- type: "object",
262
- properties: {
263
- includeInBackup: {
264
- type: "boolean"
265
- }
266
- },
267
- additionalProperties: false
268
- }
269
- },
270
- additionalProperties: false
271
- };
272
- var validate2 = ajv.compile(configSchema);
304
+ var validate2 = ajv.compile(config_schema_default);
273
305
  function validateConfig(data) {
274
306
  const valid = validate2(data);
275
307
  if (valid) return { valid: true };
@@ -429,7 +461,7 @@ function generateFilename(pattern, options) {
429
461
  var import_promises3 = require("fs/promises");
430
462
  var import_node_path5 = require("path");
431
463
  var import_picocolors = __toESM(require("picocolors"), 1);
432
- var ANSI_RE = /\x1b\[[0-9;]*m/g;
464
+ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
433
465
  function stripAnsi(str) {
434
466
  return str.replace(ANSI_RE, "");
435
467
  }
@@ -485,8 +517,11 @@ var logger = {
485
517
  var import_node_crypto = require("crypto");
486
518
  var import_promises4 = require("fs/promises");
487
519
 
488
- // src/schemas/metadata.schema.ts
489
- var metadataSchema = {
520
+ // assets/schemas/metadata.schema.json
521
+ var metadata_schema_default = {
522
+ $schema: "http://json-schema.org/draft-07/schema#",
523
+ title: "Syncpoint Backup Metadata",
524
+ description: "Metadata stored inside backup archives as _metadata.json",
490
525
  type: "object",
491
526
  required: [
492
527
  "version",
@@ -499,57 +534,114 @@ var metadataSchema = {
499
534
  "summary"
500
535
  ],
501
536
  properties: {
502
- version: { type: "string" },
503
- toolVersion: { type: "string" },
504
- createdAt: { type: "string" },
505
- hostname: { type: "string" },
537
+ version: {
538
+ type: "string",
539
+ description: "Metadata schema version."
540
+ },
541
+ toolVersion: {
542
+ type: "string",
543
+ description: "Syncpoint tool version used to create the backup."
544
+ },
545
+ createdAt: {
546
+ type: "string",
547
+ description: "ISO 8601 timestamp of backup creation."
548
+ },
549
+ hostname: {
550
+ type: "string",
551
+ description: "Hostname of the machine where the backup was created."
552
+ },
506
553
  system: {
507
554
  type: "object",
555
+ description: "System information at backup time.",
508
556
  required: ["platform", "release", "arch"],
509
557
  properties: {
510
- platform: { type: "string" },
511
- release: { type: "string" },
512
- arch: { type: "string" }
558
+ platform: {
559
+ type: "string",
560
+ description: "Operating system platform (e.g. darwin, linux)."
561
+ },
562
+ release: {
563
+ type: "string",
564
+ description: "OS kernel release version."
565
+ },
566
+ arch: {
567
+ type: "string",
568
+ description: "CPU architecture (e.g. arm64, x64)."
569
+ }
513
570
  },
514
571
  additionalProperties: false
515
572
  },
516
573
  config: {
517
574
  type: "object",
575
+ description: "Backup configuration snapshot.",
518
576
  required: ["filename"],
519
577
  properties: {
520
- filename: { type: "string" },
521
- destination: { type: "string" }
578
+ filename: {
579
+ type: "string",
580
+ description: "Filename pattern used for the backup."
581
+ },
582
+ destination: {
583
+ type: "string",
584
+ description: "Custom destination path, if configured."
585
+ }
522
586
  },
523
587
  additionalProperties: false
524
588
  },
525
589
  files: {
526
590
  type: "array",
591
+ description: "List of files included in the backup.",
527
592
  items: {
528
593
  type: "object",
529
594
  required: ["path", "absolutePath", "size", "hash"],
530
595
  properties: {
531
- path: { type: "string" },
532
- absolutePath: { type: "string" },
533
- size: { type: "number", minimum: 0 },
534
- hash: { type: "string" },
535
- type: { type: "string" }
596
+ path: {
597
+ type: "string",
598
+ description: "Display path (e.g. ~/.zshrc)."
599
+ },
600
+ absolutePath: {
601
+ type: "string",
602
+ description: "Full filesystem path."
603
+ },
604
+ size: {
605
+ type: "number",
606
+ description: "File size in bytes.",
607
+ minimum: 0
608
+ },
609
+ hash: {
610
+ type: "string",
611
+ description: "SHA-256 hash of file contents."
612
+ },
613
+ type: {
614
+ type: "string",
615
+ description: "File type (e.g. symlink, directory)."
616
+ }
536
617
  },
537
618
  additionalProperties: false
538
619
  }
539
620
  },
540
621
  summary: {
541
622
  type: "object",
623
+ description: "Backup summary statistics.",
542
624
  required: ["fileCount", "totalSize"],
543
625
  properties: {
544
- fileCount: { type: "integer", minimum: 0 },
545
- totalSize: { type: "number", minimum: 0 }
626
+ fileCount: {
627
+ type: "integer",
628
+ description: "Total number of files in the backup.",
629
+ minimum: 0
630
+ },
631
+ totalSize: {
632
+ type: "number",
633
+ description: "Total size of all files in bytes.",
634
+ minimum: 0
635
+ }
546
636
  },
547
637
  additionalProperties: false
548
638
  }
549
639
  },
550
640
  additionalProperties: false
551
641
  };
552
- var validate3 = ajv.compile(metadataSchema);
642
+
643
+ // src/schemas/metadata.schema.ts
644
+ var validate3 = ajv.compile(metadata_schema_default);
553
645
  function validateMetadata(data) {
554
646
  const valid = validate3(data);
555
647
  if (valid) return { valid: true };
@@ -560,7 +652,7 @@ function validateMetadata(data) {
560
652
  }
561
653
 
562
654
  // src/version.ts
563
- var VERSION = "0.0.3";
655
+ var VERSION = "0.0.5";
564
656
 
565
657
  // src/core/metadata.ts
566
658
  var METADATA_VERSION = "1.0.0";
@@ -611,7 +703,8 @@ async function collectFileInfo(absolutePath, logicalPath) {
611
703
  }
612
704
  let hash;
613
705
  if (lstats.isSymbolicLink()) {
614
- hash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(absolutePath).digest("hex")}`;
706
+ const linkTarget = await (0, import_promises4.readlink)(absolutePath);
707
+ hash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(linkTarget).digest("hex")}`;
615
708
  } else {
616
709
  hash = await computeFileHash(absolutePath);
617
710
  }
@@ -672,7 +765,8 @@ async function extractArchive(archivePath, destDir) {
672
765
  const normalizedPath = (0, import_node_path6.normalize)(path);
673
766
  if (normalizedPath.includes("..")) return false;
674
767
  if (normalizedPath.startsWith("/")) return false;
675
- if (entry.type === "SymbolicLink" || entry.type === "Link") return false;
768
+ if ("type" in entry && (entry.type === "SymbolicLink" || entry.type === "Link"))
769
+ return false;
676
770
  return true;
677
771
  }
678
772
  });
@@ -743,6 +837,10 @@ async function scanTargets(config) {
743
837
  });
744
838
  for (const match of allFiles) {
745
839
  if (regex.test(match) && !isExcluded(match)) {
840
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
841
+ logger.warn(`Sensitive file excluded: ${match}`);
842
+ continue;
843
+ }
746
844
  const entry = await collectFileInfo(match, match);
747
845
  found.push(entry);
748
846
  }
@@ -771,6 +869,10 @@ async function scanTargets(config) {
771
869
  });
772
870
  for (const match of matches) {
773
871
  if (!isExcluded(match)) {
872
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
873
+ logger.warn(`Sensitive file excluded: ${match}`);
874
+ continue;
875
+ }
774
876
  const entry = await collectFileInfo(match, match);
775
877
  found.push(entry);
776
878
  }
@@ -804,6 +906,10 @@ async function scanTargets(config) {
804
906
  });
805
907
  for (const match of matches) {
806
908
  if (!isExcluded(match)) {
909
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
910
+ logger.warn(`Sensitive file excluded: ${match}`);
911
+ continue;
912
+ }
807
913
  const entry = await collectFileInfo(match, match);
808
914
  found.push(entry);
809
915
  }
@@ -815,15 +921,16 @@ async function scanTargets(config) {
815
921
  if (isExcluded(absPath)) {
816
922
  continue;
817
923
  }
924
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(absPath)) {
925
+ logger.warn(`Sensitive file excluded: ${target}`);
926
+ continue;
927
+ }
818
928
  const entry = await collectFileInfo(absPath, absPath);
819
929
  if (entry.size > LARGE_FILE_THRESHOLD) {
820
930
  logger.warn(
821
931
  `Large file (>${Math.round(LARGE_FILE_THRESHOLD / 1024 / 1024)}MB): ${target}`
822
932
  );
823
933
  }
824
- if (isSensitiveFile(absPath)) {
825
- logger.warn(`Sensitive file detected: ${target}`);
826
- }
827
934
  found.push(entry);
828
935
  }
829
936
  }
@@ -857,7 +964,7 @@ async function createBackup(config, options = {}) {
857
964
  }
858
965
  }
859
966
  let allFiles = [...found];
860
- if (config.scripts.includeInBackup) {
967
+ if (config.scripts?.includeInBackup) {
861
968
  const scripts = await collectScripts();
862
969
  allFiles = [...allFiles, ...scripts];
863
970
  }
@@ -1055,27 +1162,61 @@ var import_promises8 = require("fs/promises");
1055
1162
  var import_node_path9 = require("path");
1056
1163
  var import_yaml2 = __toESM(require("yaml"), 1);
1057
1164
 
1058
- // src/schemas/template.schema.ts
1059
- var templateSchema = {
1165
+ // assets/schemas/template.schema.json
1166
+ var template_schema_default = {
1167
+ $schema: "http://json-schema.org/draft-07/schema#",
1168
+ title: "Syncpoint Template",
1169
+ description: "Provisioning template for syncpoint",
1060
1170
  type: "object",
1061
1171
  required: ["name", "steps"],
1062
1172
  properties: {
1063
- name: { type: "string", minLength: 1 },
1064
- description: { type: "string" },
1065
- backup: { type: "string" },
1066
- sudo: { type: "boolean" },
1173
+ name: {
1174
+ type: "string",
1175
+ description: "Template name.",
1176
+ minLength: 1
1177
+ },
1178
+ description: {
1179
+ type: "string",
1180
+ description: "Template description."
1181
+ },
1182
+ backup: {
1183
+ type: "string",
1184
+ description: "Backup name to restore automatically after provisioning."
1185
+ },
1186
+ sudo: {
1187
+ type: "boolean",
1188
+ description: "Whether sudo privilege is required. If true, requests sudo authentication before execution."
1189
+ },
1067
1190
  steps: {
1068
1191
  type: "array",
1192
+ description: "List of provisioning steps. At least 1 step required.",
1069
1193
  minItems: 1,
1070
1194
  items: {
1071
1195
  type: "object",
1072
1196
  required: ["name", "command"],
1073
1197
  properties: {
1074
- name: { type: "string", minLength: 1 },
1075
- description: { type: "string" },
1076
- command: { type: "string", minLength: 1 },
1077
- skip_if: { type: "string" },
1078
- continue_on_error: { type: "boolean" }
1198
+ name: {
1199
+ type: "string",
1200
+ description: "Step name.",
1201
+ minLength: 1
1202
+ },
1203
+ description: {
1204
+ type: "string",
1205
+ description: "Step description."
1206
+ },
1207
+ command: {
1208
+ type: "string",
1209
+ description: "Shell command to execute.",
1210
+ minLength: 1
1211
+ },
1212
+ skip_if: {
1213
+ type: "string",
1214
+ description: "Skip this step if this command exits with code 0."
1215
+ },
1216
+ continue_on_error: {
1217
+ type: "boolean",
1218
+ description: "Continue to next step even if this fails. Default: false."
1219
+ }
1079
1220
  },
1080
1221
  additionalProperties: false
1081
1222
  }
@@ -1083,7 +1224,9 @@ var templateSchema = {
1083
1224
  },
1084
1225
  additionalProperties: false
1085
1226
  };
1086
- var validate4 = ajv.compile(templateSchema);
1227
+
1228
+ // src/schemas/template.schema.ts
1229
+ var validate4 = ajv.compile(template_schema_default);
1087
1230
  function validateTemplate(data) {
1088
1231
  const valid = validate4(data);
1089
1232
  if (valid) return { valid: true };
@@ -1209,8 +1352,10 @@ async function executeStep(step) {
1209
1352
  output: output || void 0
1210
1353
  };
1211
1354
  } catch (err) {
1212
- const error = err;
1213
- const errorOutput = [error.stdout, error.stderr, error.message].filter(Boolean).join("\n").trim();
1355
+ const error = err instanceof Error ? err : new Error(String(err));
1356
+ const stdout = err?.stdout ?? "";
1357
+ const stderr = err?.stderr ?? "";
1358
+ const errorOutput = [stdout, stderr, error.message].filter(Boolean).join("\n").trim();
1214
1359
  return {
1215
1360
  name: step.name,
1216
1361
  status: "failed",