@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.
package/dist/index.mjs CHANGED
@@ -76,6 +76,76 @@ function getSubDir(sub) {
76
76
  return join2(getAppDir(), sub);
77
77
  }
78
78
 
79
+ // assets/schemas/config.schema.json
80
+ var config_schema_default = {
81
+ $schema: "http://json-schema.org/draft-07/schema#",
82
+ title: "Syncpoint Config",
83
+ description: "Configuration for syncpoint backup tool",
84
+ type: "object",
85
+ required: [
86
+ "backup"
87
+ ],
88
+ properties: {
89
+ backup: {
90
+ type: "object",
91
+ description: "Backup configuration",
92
+ required: [
93
+ "targets",
94
+ "exclude",
95
+ "filename"
96
+ ],
97
+ properties: {
98
+ targets: {
99
+ type: "array",
100
+ 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$/).",
101
+ items: {
102
+ type: "string",
103
+ validPattern: true
104
+ }
105
+ },
106
+ exclude: {
107
+ type: "array",
108
+ description: "List of patterns to exclude from backup. Supports glob (e.g. **/*.swp) and regex (e.g. /\\.bak$/) patterns.",
109
+ items: {
110
+ type: "string",
111
+ validPattern: true
112
+ }
113
+ },
114
+ filename: {
115
+ type: "string",
116
+ description: "Backup archive filename pattern. Available variables: {hostname}, {datetime}.",
117
+ minLength: 1
118
+ },
119
+ destination: {
120
+ type: "string",
121
+ description: "Backup archive destination path. Default: ~/.syncpoint/backups/"
122
+ },
123
+ includeSensitiveFiles: {
124
+ type: "boolean",
125
+ 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."
126
+ }
127
+ },
128
+ additionalProperties: false
129
+ },
130
+ scripts: {
131
+ type: "object",
132
+ description: "Scripts configuration",
133
+ properties: {
134
+ includeInBackup: {
135
+ type: "boolean",
136
+ description: "Whether to include scripts/ directory in backup. Default: true."
137
+ }
138
+ },
139
+ additionalProperties: false
140
+ },
141
+ "yaml-language-server": {
142
+ type: "string",
143
+ description: "Editor directive for schema association; ignored at runtime."
144
+ }
145
+ },
146
+ additionalProperties: false
147
+ };
148
+
79
149
  // src/schemas/ajv.ts
80
150
  import Ajv from "ajv";
81
151
  import addFormats from "ajv-formats";
@@ -181,45 +251,7 @@ ajv.addKeyword({
181
251
  });
182
252
 
183
253
  // src/schemas/config.schema.ts
184
- var configSchema = {
185
- type: "object",
186
- required: ["backup"],
187
- properties: {
188
- backup: {
189
- type: "object",
190
- required: ["targets", "exclude", "filename"],
191
- properties: {
192
- targets: {
193
- type: "array",
194
- items: { type: "string", validPattern: true }
195
- },
196
- exclude: {
197
- type: "array",
198
- items: { type: "string", validPattern: true }
199
- },
200
- filename: {
201
- type: "string",
202
- minLength: 1
203
- },
204
- destination: {
205
- type: "string"
206
- }
207
- },
208
- additionalProperties: false
209
- },
210
- scripts: {
211
- type: "object",
212
- properties: {
213
- includeInBackup: {
214
- type: "boolean"
215
- }
216
- },
217
- additionalProperties: false
218
- }
219
- },
220
- additionalProperties: false
221
- };
222
- var validate2 = ajv.compile(configSchema);
254
+ var validate2 = ajv.compile(config_schema_default);
223
255
  function validateConfig(data) {
224
256
  const valid = validate2(data);
225
257
  if (valid) return { valid: true };
@@ -379,7 +411,7 @@ function generateFilename(pattern, options) {
379
411
  import { appendFile, mkdir as mkdir2 } from "fs/promises";
380
412
  import { join as join5 } from "path";
381
413
  import pc from "picocolors";
382
- var ANSI_RE = /\x1b\[[0-9;]*m/g;
414
+ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
383
415
  function stripAnsi(str) {
384
416
  return str.replace(ANSI_RE, "");
385
417
  }
@@ -433,10 +465,13 @@ var logger = {
433
465
 
434
466
  // src/core/metadata.ts
435
467
  import { createHash } from "crypto";
436
- import { lstat, readFile as readFile2 } from "fs/promises";
468
+ import { lstat, readFile as readFile2, readlink } from "fs/promises";
437
469
 
438
- // src/schemas/metadata.schema.ts
439
- var metadataSchema = {
470
+ // assets/schemas/metadata.schema.json
471
+ var metadata_schema_default = {
472
+ $schema: "http://json-schema.org/draft-07/schema#",
473
+ title: "Syncpoint Backup Metadata",
474
+ description: "Metadata stored inside backup archives as _metadata.json",
440
475
  type: "object",
441
476
  required: [
442
477
  "version",
@@ -449,57 +484,114 @@ var metadataSchema = {
449
484
  "summary"
450
485
  ],
451
486
  properties: {
452
- version: { type: "string" },
453
- toolVersion: { type: "string" },
454
- createdAt: { type: "string" },
455
- hostname: { type: "string" },
487
+ version: {
488
+ type: "string",
489
+ description: "Metadata schema version."
490
+ },
491
+ toolVersion: {
492
+ type: "string",
493
+ description: "Syncpoint tool version used to create the backup."
494
+ },
495
+ createdAt: {
496
+ type: "string",
497
+ description: "ISO 8601 timestamp of backup creation."
498
+ },
499
+ hostname: {
500
+ type: "string",
501
+ description: "Hostname of the machine where the backup was created."
502
+ },
456
503
  system: {
457
504
  type: "object",
505
+ description: "System information at backup time.",
458
506
  required: ["platform", "release", "arch"],
459
507
  properties: {
460
- platform: { type: "string" },
461
- release: { type: "string" },
462
- arch: { type: "string" }
508
+ platform: {
509
+ type: "string",
510
+ description: "Operating system platform (e.g. darwin, linux)."
511
+ },
512
+ release: {
513
+ type: "string",
514
+ description: "OS kernel release version."
515
+ },
516
+ arch: {
517
+ type: "string",
518
+ description: "CPU architecture (e.g. arm64, x64)."
519
+ }
463
520
  },
464
521
  additionalProperties: false
465
522
  },
466
523
  config: {
467
524
  type: "object",
525
+ description: "Backup configuration snapshot.",
468
526
  required: ["filename"],
469
527
  properties: {
470
- filename: { type: "string" },
471
- destination: { type: "string" }
528
+ filename: {
529
+ type: "string",
530
+ description: "Filename pattern used for the backup."
531
+ },
532
+ destination: {
533
+ type: "string",
534
+ description: "Custom destination path, if configured."
535
+ }
472
536
  },
473
537
  additionalProperties: false
474
538
  },
475
539
  files: {
476
540
  type: "array",
541
+ description: "List of files included in the backup.",
477
542
  items: {
478
543
  type: "object",
479
544
  required: ["path", "absolutePath", "size", "hash"],
480
545
  properties: {
481
- path: { type: "string" },
482
- absolutePath: { type: "string" },
483
- size: { type: "number", minimum: 0 },
484
- hash: { type: "string" },
485
- type: { type: "string" }
546
+ path: {
547
+ type: "string",
548
+ description: "Display path (e.g. ~/.zshrc)."
549
+ },
550
+ absolutePath: {
551
+ type: "string",
552
+ description: "Full filesystem path."
553
+ },
554
+ size: {
555
+ type: "number",
556
+ description: "File size in bytes.",
557
+ minimum: 0
558
+ },
559
+ hash: {
560
+ type: "string",
561
+ description: "SHA-256 hash of file contents."
562
+ },
563
+ type: {
564
+ type: "string",
565
+ description: "File type (e.g. symlink, directory)."
566
+ }
486
567
  },
487
568
  additionalProperties: false
488
569
  }
489
570
  },
490
571
  summary: {
491
572
  type: "object",
573
+ description: "Backup summary statistics.",
492
574
  required: ["fileCount", "totalSize"],
493
575
  properties: {
494
- fileCount: { type: "integer", minimum: 0 },
495
- totalSize: { type: "number", minimum: 0 }
576
+ fileCount: {
577
+ type: "integer",
578
+ description: "Total number of files in the backup.",
579
+ minimum: 0
580
+ },
581
+ totalSize: {
582
+ type: "number",
583
+ description: "Total size of all files in bytes.",
584
+ minimum: 0
585
+ }
496
586
  },
497
587
  additionalProperties: false
498
588
  }
499
589
  },
500
590
  additionalProperties: false
501
591
  };
502
- var validate3 = ajv.compile(metadataSchema);
592
+
593
+ // src/schemas/metadata.schema.ts
594
+ var validate3 = ajv.compile(metadata_schema_default);
503
595
  function validateMetadata(data) {
504
596
  const valid = validate3(data);
505
597
  if (valid) return { valid: true };
@@ -510,7 +602,7 @@ function validateMetadata(data) {
510
602
  }
511
603
 
512
604
  // src/version.ts
513
- var VERSION = "0.0.3";
605
+ var VERSION = "0.0.5";
514
606
 
515
607
  // src/core/metadata.ts
516
608
  var METADATA_VERSION = "1.0.0";
@@ -561,7 +653,8 @@ async function collectFileInfo(absolutePath, logicalPath) {
561
653
  }
562
654
  let hash;
563
655
  if (lstats.isSymbolicLink()) {
564
- hash = `sha256:${createHash("sha256").update(absolutePath).digest("hex")}`;
656
+ const linkTarget = await readlink(absolutePath);
657
+ hash = `sha256:${createHash("sha256").update(linkTarget).digest("hex")}`;
565
658
  } else {
566
659
  hash = await computeFileHash(absolutePath);
567
660
  }
@@ -622,7 +715,8 @@ async function extractArchive(archivePath, destDir) {
622
715
  const normalizedPath = normalize2(path);
623
716
  if (normalizedPath.includes("..")) return false;
624
717
  if (normalizedPath.startsWith("/")) return false;
625
- if (entry.type === "SymbolicLink" || entry.type === "Link") return false;
718
+ if ("type" in entry && (entry.type === "SymbolicLink" || entry.type === "Link"))
719
+ return false;
626
720
  return true;
627
721
  }
628
722
  });
@@ -693,6 +787,10 @@ async function scanTargets(config) {
693
787
  });
694
788
  for (const match of allFiles) {
695
789
  if (regex.test(match) && !isExcluded(match)) {
790
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
791
+ logger.warn(`Sensitive file excluded: ${match}`);
792
+ continue;
793
+ }
696
794
  const entry = await collectFileInfo(match, match);
697
795
  found.push(entry);
698
796
  }
@@ -721,6 +819,10 @@ async function scanTargets(config) {
721
819
  });
722
820
  for (const match of matches) {
723
821
  if (!isExcluded(match)) {
822
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
823
+ logger.warn(`Sensitive file excluded: ${match}`);
824
+ continue;
825
+ }
724
826
  const entry = await collectFileInfo(match, match);
725
827
  found.push(entry);
726
828
  }
@@ -754,6 +856,10 @@ async function scanTargets(config) {
754
856
  });
755
857
  for (const match of matches) {
756
858
  if (!isExcluded(match)) {
859
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
860
+ logger.warn(`Sensitive file excluded: ${match}`);
861
+ continue;
862
+ }
757
863
  const entry = await collectFileInfo(match, match);
758
864
  found.push(entry);
759
865
  }
@@ -765,15 +871,16 @@ async function scanTargets(config) {
765
871
  if (isExcluded(absPath)) {
766
872
  continue;
767
873
  }
874
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(absPath)) {
875
+ logger.warn(`Sensitive file excluded: ${target}`);
876
+ continue;
877
+ }
768
878
  const entry = await collectFileInfo(absPath, absPath);
769
879
  if (entry.size > LARGE_FILE_THRESHOLD) {
770
880
  logger.warn(
771
881
  `Large file (>${Math.round(LARGE_FILE_THRESHOLD / 1024 / 1024)}MB): ${target}`
772
882
  );
773
883
  }
774
- if (isSensitiveFile(absPath)) {
775
- logger.warn(`Sensitive file detected: ${target}`);
776
- }
777
884
  found.push(entry);
778
885
  }
779
886
  }
@@ -807,7 +914,7 @@ async function createBackup(config, options = {}) {
807
914
  }
808
915
  }
809
916
  let allFiles = [...found];
810
- if (config.scripts.includeInBackup) {
917
+ if (config.scripts?.includeInBackup) {
811
918
  const scripts = await collectScripts();
812
919
  allFiles = [...allFiles, ...scripts];
813
920
  }
@@ -1005,27 +1112,61 @@ import { readFile as readFile4, readdir as readdir3 } from "fs/promises";
1005
1112
  import { join as join9 } from "path";
1006
1113
  import YAML2 from "yaml";
1007
1114
 
1008
- // src/schemas/template.schema.ts
1009
- var templateSchema = {
1115
+ // assets/schemas/template.schema.json
1116
+ var template_schema_default = {
1117
+ $schema: "http://json-schema.org/draft-07/schema#",
1118
+ title: "Syncpoint Template",
1119
+ description: "Provisioning template for syncpoint",
1010
1120
  type: "object",
1011
1121
  required: ["name", "steps"],
1012
1122
  properties: {
1013
- name: { type: "string", minLength: 1 },
1014
- description: { type: "string" },
1015
- backup: { type: "string" },
1016
- sudo: { type: "boolean" },
1123
+ name: {
1124
+ type: "string",
1125
+ description: "Template name.",
1126
+ minLength: 1
1127
+ },
1128
+ description: {
1129
+ type: "string",
1130
+ description: "Template description."
1131
+ },
1132
+ backup: {
1133
+ type: "string",
1134
+ description: "Backup name to restore automatically after provisioning."
1135
+ },
1136
+ sudo: {
1137
+ type: "boolean",
1138
+ description: "Whether sudo privilege is required. If true, requests sudo authentication before execution."
1139
+ },
1017
1140
  steps: {
1018
1141
  type: "array",
1142
+ description: "List of provisioning steps. At least 1 step required.",
1019
1143
  minItems: 1,
1020
1144
  items: {
1021
1145
  type: "object",
1022
1146
  required: ["name", "command"],
1023
1147
  properties: {
1024
- name: { type: "string", minLength: 1 },
1025
- description: { type: "string" },
1026
- command: { type: "string", minLength: 1 },
1027
- skip_if: { type: "string" },
1028
- continue_on_error: { type: "boolean" }
1148
+ name: {
1149
+ type: "string",
1150
+ description: "Step name.",
1151
+ minLength: 1
1152
+ },
1153
+ description: {
1154
+ type: "string",
1155
+ description: "Step description."
1156
+ },
1157
+ command: {
1158
+ type: "string",
1159
+ description: "Shell command to execute.",
1160
+ minLength: 1
1161
+ },
1162
+ skip_if: {
1163
+ type: "string",
1164
+ description: "Skip this step if this command exits with code 0."
1165
+ },
1166
+ continue_on_error: {
1167
+ type: "boolean",
1168
+ description: "Continue to next step even if this fails. Default: false."
1169
+ }
1029
1170
  },
1030
1171
  additionalProperties: false
1031
1172
  }
@@ -1033,7 +1174,9 @@ var templateSchema = {
1033
1174
  },
1034
1175
  additionalProperties: false
1035
1176
  };
1036
- var validate4 = ajv.compile(templateSchema);
1177
+
1178
+ // src/schemas/template.schema.ts
1179
+ var validate4 = ajv.compile(template_schema_default);
1037
1180
  function validateTemplate(data) {
1038
1181
  const valid = validate4(data);
1039
1182
  if (valid) return { valid: true };
@@ -1159,8 +1302,10 @@ async function executeStep(step) {
1159
1302
  output: output || void 0
1160
1303
  };
1161
1304
  } catch (err) {
1162
- const error = err;
1163
- const errorOutput = [error.stdout, error.stderr, error.message].filter(Boolean).join("\n").trim();
1305
+ const error = err instanceof Error ? err : new Error(String(err));
1306
+ const stdout = err?.stdout ?? "";
1307
+ const stderr = err?.stderr ?? "";
1308
+ const errorOutput = [stdout, stderr, error.message].filter(Boolean).join("\n").trim();
1164
1309
  return {
1165
1310
  name: step.name,
1166
1311
  status: "failed",
@@ -1,9 +1,12 @@
1
1
  export interface SyncpointConfig {
2
+ /** Editor directive for schema association; ignored at runtime. */
3
+ 'yaml-language-server'?: string;
2
4
  backup: {
3
5
  targets: string[];
4
6
  exclude: string[];
5
7
  filename: string;
6
8
  destination?: string;
9
+ includeSensitiveFiles?: boolean;
7
10
  };
8
11
  scripts: {
9
12
  includeInBackup: boolean;
@@ -38,7 +41,7 @@ export interface FileEntry {
38
41
  }
39
42
  export interface TemplateConfig {
40
43
  name: string;
41
- description: string;
44
+ description?: string;
42
45
  backup?: string;
43
46
  sudo?: boolean;
44
47
  steps: TemplateStep[];
@@ -81,6 +84,13 @@ export interface StepResult {
81
84
  error?: string;
82
85
  output?: string;
83
86
  }
87
+ export interface MigrateResult {
88
+ added: string[];
89
+ deprecated: string[];
90
+ preserved: string[];
91
+ backupPath: string;
92
+ migrated: boolean;
93
+ }
84
94
  export interface BackupOptions {
85
95
  dryRun?: boolean;
86
96
  tag?: 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.3";
5
+ export declare const VERSION = "0.0.5";
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.5",
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"