@lumy-pack/syncpoint 0.0.8 → 0.0.9
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/LICENSE +21 -0
- package/README.md +69 -38
- package/assets/schemas/config.schema.json +3 -9
- package/dist/cli.mjs +214 -165
- package/dist/commands/CreateTemplate.d.ts +1 -1
- package/dist/commands/Init.d.ts +1 -1
- package/dist/commands/Provision.d.ts +1 -1
- package/dist/commands/Restore.d.ts +1 -1
- package/dist/components/ProgressBar.d.ts +1 -1
- package/dist/components/StepRunner.d.ts +2 -2
- package/dist/components/Table.d.ts +1 -1
- package/dist/components/Viewer.d.ts +1 -1
- package/dist/index.cjs +2 -8
- package/dist/index.mjs +2 -8
- package/package.json +4 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Vincent K. Kelvin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -73,6 +73,7 @@ The fastest and easiest way to get started. AI automatically generates your conf
|
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
**Tip:** If you don't have Claude Code, use `--print` to get the prompt for any LLM:
|
|
76
|
+
|
|
76
77
|
```bash
|
|
77
78
|
npx @lumy-pack/syncpoint wizard --print
|
|
78
79
|
```
|
|
@@ -100,8 +101,8 @@ If you prefer to manually edit your configuration file, use this approach.
|
|
|
100
101
|
- ~/.gitconfig
|
|
101
102
|
- ~/.ssh/config
|
|
102
103
|
exclude:
|
|
103
|
-
-
|
|
104
|
-
filename:
|
|
104
|
+
- '**/*.swp'
|
|
105
|
+
filename: '{hostname}_{datetime}'
|
|
105
106
|
```
|
|
106
107
|
|
|
107
108
|
3. **Create your first backup**
|
|
@@ -137,6 +138,7 @@ npx @lumy-pack/syncpoint provision my-dev-setup
|
|
|
137
138
|
Initialize the syncpoint directory structure and create default configuration.
|
|
138
139
|
|
|
139
140
|
**What it does:**
|
|
141
|
+
|
|
140
142
|
- Creates `~/.syncpoint/` directory structure
|
|
141
143
|
- Sets up subdirectories: `backups/`, `templates/`, `scripts/`, `logs/`
|
|
142
144
|
- Generates default `config.yml`
|
|
@@ -155,6 +157,7 @@ npx @lumy-pack/syncpoint init
|
|
|
155
157
|
Interactive LLM-powered wizard to generate personalized `config.yml` based on your home directory.
|
|
156
158
|
|
|
157
159
|
**What it does:**
|
|
160
|
+
|
|
158
161
|
1. Scans your home directory for common configuration files
|
|
159
162
|
2. Categorizes files (shell configs, git, SSH, application configs)
|
|
160
163
|
3. Invokes Claude Code to generate customized backup configuration
|
|
@@ -164,8 +167,8 @@ Interactive LLM-powered wizard to generate personalized `config.yml` based on yo
|
|
|
164
167
|
|
|
165
168
|
**Options:**
|
|
166
169
|
|
|
167
|
-
| Option
|
|
168
|
-
|
|
170
|
+
| Option | Description |
|
|
171
|
+
| ------------- | ----------------------------------------------------------------- |
|
|
169
172
|
| `-p, --print` | Print prompt for manual LLM usage instead of invoking Claude Code |
|
|
170
173
|
|
|
171
174
|
**Usage:**
|
|
@@ -179,10 +182,12 @@ npx @lumy-pack/syncpoint wizard --print
|
|
|
179
182
|
```
|
|
180
183
|
|
|
181
184
|
**Requirements:**
|
|
185
|
+
|
|
182
186
|
- Claude Code CLI must be installed for default mode
|
|
183
187
|
- Use `--print` mode if Claude Code is not available
|
|
184
188
|
|
|
185
189
|
**Validation:**
|
|
190
|
+
|
|
186
191
|
- Automatic AJV schema validation
|
|
187
192
|
- Retry loop with error feedback to LLM
|
|
188
193
|
- Session resume preserves conversation context
|
|
@@ -194,6 +199,7 @@ npx @lumy-pack/syncpoint wizard --print
|
|
|
194
199
|
Interactive LLM-powered wizard to create custom provisioning templates.
|
|
195
200
|
|
|
196
201
|
**What it does:**
|
|
202
|
+
|
|
197
203
|
1. Guides you through defining provisioning requirements
|
|
198
204
|
2. Invokes Claude Code to generate template YAML
|
|
199
205
|
3. Validates template structure with automatic retry (max 3 attempts)
|
|
@@ -202,8 +208,8 @@ Interactive LLM-powered wizard to create custom provisioning templates.
|
|
|
202
208
|
|
|
203
209
|
**Options:**
|
|
204
210
|
|
|
205
|
-
| Option
|
|
206
|
-
|
|
211
|
+
| Option | Description |
|
|
212
|
+
| ------------- | ----------------------------------------------------------------- |
|
|
207
213
|
| `-p, --print` | Print prompt for manual LLM usage instead of invoking Claude Code |
|
|
208
214
|
|
|
209
215
|
**Usage:**
|
|
@@ -220,6 +226,7 @@ npx @lumy-pack/syncpoint create-template --print
|
|
|
220
226
|
```
|
|
221
227
|
|
|
222
228
|
**Template Fields:**
|
|
229
|
+
|
|
223
230
|
- `name` (required) — Template name
|
|
224
231
|
- `description` (optional) — Template description
|
|
225
232
|
- `steps` (required) — Array of provisioning steps
|
|
@@ -227,6 +234,7 @@ npx @lumy-pack/syncpoint create-template --print
|
|
|
227
234
|
- `sudo` (optional) — Whether sudo privilege is required
|
|
228
235
|
|
|
229
236
|
**Step Fields:**
|
|
237
|
+
|
|
230
238
|
- `name` (required) — Step name
|
|
231
239
|
- `command` (required) — Shell command to execute
|
|
232
240
|
- `description` (optional) — Step description
|
|
@@ -240,6 +248,7 @@ npx @lumy-pack/syncpoint create-template --print
|
|
|
240
248
|
Create a compressed backup archive of your configuration files.
|
|
241
249
|
|
|
242
250
|
**What it does:**
|
|
251
|
+
|
|
243
252
|
1. Scans configured target files and directories
|
|
244
253
|
2. Applies glob patterns and exclusions
|
|
245
254
|
3. Warns about large files (>10MB) and sensitive files (SSH keys, certificates)
|
|
@@ -249,10 +258,10 @@ Create a compressed backup archive of your configuration files.
|
|
|
249
258
|
|
|
250
259
|
**Options:**
|
|
251
260
|
|
|
252
|
-
| Option
|
|
253
|
-
|
|
254
|
-
| `--dry-run`
|
|
255
|
-
| `--tag <name>` | Add custom tag to backup filename
|
|
261
|
+
| Option | Description |
|
|
262
|
+
| -------------- | ------------------------------------------------------ |
|
|
263
|
+
| `--dry-run` | Preview files to be backed up without creating archive |
|
|
264
|
+
| `--tag <name>` | Add custom tag to backup filename |
|
|
256
265
|
|
|
257
266
|
**Usage:**
|
|
258
267
|
|
|
@@ -278,6 +287,7 @@ Backups are saved to `~/.syncpoint/backups/` (or custom destination) with filena
|
|
|
278
287
|
Restore configuration files from a backup archive.
|
|
279
288
|
|
|
280
289
|
**What it does:**
|
|
290
|
+
|
|
281
291
|
1. Lists available backups (if no filename provided)
|
|
282
292
|
2. Generates restore plan by comparing file hashes:
|
|
283
293
|
- `create` — File doesn't exist locally
|
|
@@ -289,8 +299,8 @@ Restore configuration files from a backup archive.
|
|
|
289
299
|
|
|
290
300
|
**Options:**
|
|
291
301
|
|
|
292
|
-
| Option
|
|
293
|
-
|
|
302
|
+
| Option | Description |
|
|
303
|
+
| ----------- | -------------------------------------------- |
|
|
294
304
|
| `--dry-run` | Show restore plan without actually restoring |
|
|
295
305
|
|
|
296
306
|
**Usage:**
|
|
@@ -307,6 +317,7 @@ npx @lumy-pack/syncpoint restore --dry-run
|
|
|
307
317
|
```
|
|
308
318
|
|
|
309
319
|
**Safety Features:**
|
|
320
|
+
|
|
310
321
|
- Automatic safety backup before any file is overwritten
|
|
311
322
|
- Hash-based comparison to skip identical files
|
|
312
323
|
- Symlink validation to prevent directory traversal attacks
|
|
@@ -318,6 +329,7 @@ npx @lumy-pack/syncpoint restore --dry-run
|
|
|
318
329
|
Run template-based machine provisioning to install software and configure your system.
|
|
319
330
|
|
|
320
331
|
**What it does:**
|
|
332
|
+
|
|
321
333
|
1. Loads template YAML from `~/.syncpoint/templates/` (by name) or from a custom path (with `--file`)
|
|
322
334
|
2. Validates template structure and security
|
|
323
335
|
3. Checks for sudo requirement (prompts if needed)
|
|
@@ -328,11 +340,11 @@ Run template-based machine provisioning to install software and configure your s
|
|
|
328
340
|
|
|
329
341
|
**Options:**
|
|
330
342
|
|
|
331
|
-
| Option
|
|
332
|
-
|
|
343
|
+
| Option | Description |
|
|
344
|
+
| ------------------- | ---------------------------------------------------- |
|
|
333
345
|
| `-f, --file <path>` | Path to template file (alternative to template name) |
|
|
334
|
-
| `--dry-run`
|
|
335
|
-
| `--skip-restore`
|
|
346
|
+
| `--dry-run` | Show execution plan without running commands |
|
|
347
|
+
| `--skip-restore` | Skip automatic config restore after provisioning |
|
|
336
348
|
|
|
337
349
|
**Usage:**
|
|
338
350
|
|
|
@@ -357,12 +369,14 @@ npx @lumy-pack/syncpoint provision -f ./template.yml --dry-run --skip-restore
|
|
|
357
369
|
```
|
|
358
370
|
|
|
359
371
|
**Path Resolution:**
|
|
372
|
+
|
|
360
373
|
- Supports absolute paths: `/path/to/template.yml`
|
|
361
374
|
- Supports relative paths: `./template.yml`, `../templates/setup.yaml`
|
|
362
375
|
- Supports tilde expansion: `~/templates/custom.yml`
|
|
363
376
|
- Must have `.yml` or `.yaml` extension
|
|
364
377
|
|
|
365
378
|
**Security:**
|
|
379
|
+
|
|
366
380
|
- Blocks dangerous remote script patterns (`curl | bash`, `wget | sh`)
|
|
367
381
|
- Sanitizes error output to mask sensitive paths and credentials
|
|
368
382
|
- Validates all templates against schema
|
|
@@ -375,6 +389,7 @@ npx @lumy-pack/syncpoint provision -f ./template.yml --dry-run --skip-restore
|
|
|
375
389
|
Browse and manage backups and templates interactively.
|
|
376
390
|
|
|
377
391
|
**What it does:**
|
|
392
|
+
|
|
378
393
|
- Displays interactive menu to browse backups or templates
|
|
379
394
|
- Shows detailed metadata (size, date, file count, description)
|
|
380
395
|
- Allows safe deletion of backups with confirmation
|
|
@@ -392,6 +407,7 @@ npx @lumy-pack/syncpoint list templates
|
|
|
392
407
|
```
|
|
393
408
|
|
|
394
409
|
**Navigation:**
|
|
410
|
+
|
|
395
411
|
- Use arrow keys to select items
|
|
396
412
|
- Press Enter to view details
|
|
397
413
|
- Press ESC to go back
|
|
@@ -404,6 +420,7 @@ npx @lumy-pack/syncpoint list templates
|
|
|
404
420
|
Show status summary and manage cleanup of `~/.syncpoint/` directory.
|
|
405
421
|
|
|
406
422
|
**What it does:**
|
|
423
|
+
|
|
407
424
|
- Scans all subdirectories and calculates statistics
|
|
408
425
|
- Displays file counts and total sizes
|
|
409
426
|
- Shows backup timeline (newest and oldest)
|
|
@@ -411,8 +428,8 @@ Show status summary and manage cleanup of `~/.syncpoint/` directory.
|
|
|
411
428
|
|
|
412
429
|
**Options:**
|
|
413
430
|
|
|
414
|
-
| Option
|
|
415
|
-
|
|
431
|
+
| Option | Description |
|
|
432
|
+
| ----------- | ------------------------------ |
|
|
416
433
|
| `--cleanup` | Enter interactive cleanup mode |
|
|
417
434
|
|
|
418
435
|
**Usage:**
|
|
@@ -426,6 +443,7 @@ npx @lumy-pack/syncpoint status --cleanup
|
|
|
426
443
|
```
|
|
427
444
|
|
|
428
445
|
**Cleanup Strategies:**
|
|
446
|
+
|
|
429
447
|
- Keep only 5 most recent backups
|
|
430
448
|
- Remove backups older than 30 days
|
|
431
449
|
- Delete all log files
|
|
@@ -457,14 +475,14 @@ backup:
|
|
|
457
475
|
# (Required) Patterns to exclude from backup
|
|
458
476
|
# Supports glob and regex patterns
|
|
459
477
|
exclude:
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
-
|
|
478
|
+
- '**/*.swp'
|
|
479
|
+
- '**/.DS_Store'
|
|
480
|
+
- '**/node_modules'
|
|
463
481
|
# Example regex: "/\\.bak$/" excludes all .bak files
|
|
464
482
|
|
|
465
483
|
# (Required) Backup filename pattern
|
|
466
484
|
# Available placeholders: {hostname}, {date}, {time}, {datetime}, {tag}
|
|
467
|
-
filename:
|
|
485
|
+
filename: '{hostname}_{datetime}'
|
|
468
486
|
|
|
469
487
|
# (Optional) Custom backup destination
|
|
470
488
|
# Default: ~/.syncpoint/backups/
|
|
@@ -478,13 +496,13 @@ scripts:
|
|
|
478
496
|
|
|
479
497
|
### Filename Placeholders
|
|
480
498
|
|
|
481
|
-
| Placeholder
|
|
482
|
-
|
|
483
|
-
| `{hostname}` | `macbook-pro`
|
|
484
|
-
| `{date}`
|
|
485
|
-
| `{time}`
|
|
486
|
-
| `{datetime}` | `2024-01-15_14-30-00` | Combined date and time
|
|
487
|
-
| `{tag}`
|
|
499
|
+
| Placeholder | Example | Description |
|
|
500
|
+
| ------------ | --------------------- | ------------------------------ |
|
|
501
|
+
| `{hostname}` | `macbook-pro` | System hostname |
|
|
502
|
+
| `{date}` | `2024-01-15` | Current date (YYYY-MM-DD) |
|
|
503
|
+
| `{time}` | `14-30-00` | Current time (HH-MM-SS) |
|
|
504
|
+
| `{datetime}` | `2024-01-15_14-30-00` | Combined date and time |
|
|
505
|
+
| `{tag}` | `before-upgrade` | Custom tag from `--tag` option |
|
|
488
506
|
|
|
489
507
|
### Pattern Types
|
|
490
508
|
|
|
@@ -495,6 +513,7 @@ Syncpoint supports three types of patterns for `targets` and `exclude` fields:
|
|
|
495
513
|
Direct file or directory paths. Tilde (`~`) is automatically expanded to home directory.
|
|
496
514
|
|
|
497
515
|
**Examples:**
|
|
516
|
+
|
|
498
517
|
- `~/.zshrc` — Specific file in home directory
|
|
499
518
|
- `/etc/hosts` — Absolute path
|
|
500
519
|
- `~/.ssh/config` — Nested file
|
|
@@ -504,6 +523,7 @@ Direct file or directory paths. Tilde (`~`) is automatically expanded to home di
|
|
|
504
523
|
Wildcard patterns for matching multiple files. Uses standard glob syntax.
|
|
505
524
|
|
|
506
525
|
**Examples:**
|
|
526
|
+
|
|
507
527
|
- `*.conf` — All .conf files in current directory
|
|
508
528
|
- `~/.config/*.yml` — All .yml files in ~/.config/
|
|
509
529
|
- `**/*.toml` — All .toml files recursively
|
|
@@ -518,17 +538,20 @@ Regular expressions for advanced pattern matching. Must be enclosed in forward s
|
|
|
518
538
|
**Format:** `/pattern/` (e.g., `/\.conf$/`)
|
|
519
539
|
|
|
520
540
|
**Examples:**
|
|
541
|
+
|
|
521
542
|
- `/\.conf$/` — Files ending with .conf
|
|
522
543
|
- `/\.toml$/` — Files ending with .toml
|
|
523
544
|
- `/\.(bak|tmp)$/` — Files ending with .bak or .tmp
|
|
524
545
|
- `/^\.config\//` — Files starting with .config/
|
|
525
546
|
|
|
526
547
|
**Limitations:**
|
|
548
|
+
|
|
527
549
|
- Regex targets scan home directory (`~/`) only
|
|
528
550
|
- Maximum depth: 5 levels for performance
|
|
529
551
|
- No unescaped forward slashes in pattern body
|
|
530
552
|
|
|
531
553
|
**When to use regex:**
|
|
554
|
+
|
|
532
555
|
- Complex extension matching: `/\.(conf|toml|yaml)$/`
|
|
533
556
|
- Pattern-based exclusions: `/\.(bak|tmp|cache)$/`
|
|
534
557
|
- Path prefix/suffix matching
|
|
@@ -547,12 +570,12 @@ backup:
|
|
|
547
570
|
- ~/Documents/notes
|
|
548
571
|
|
|
549
572
|
exclude:
|
|
550
|
-
-
|
|
551
|
-
-
|
|
552
|
-
-
|
|
553
|
-
-
|
|
573
|
+
- '**/*.swp'
|
|
574
|
+
- '**/*.tmp'
|
|
575
|
+
- '**/.DS_Store'
|
|
576
|
+
- '**/*cache*'
|
|
554
577
|
|
|
555
|
-
filename:
|
|
578
|
+
filename: '{hostname}_{date}_{tag}'
|
|
556
579
|
destination: ~/Dropbox/backups
|
|
557
580
|
|
|
558
581
|
scripts:
|
|
@@ -582,10 +605,10 @@ sudo: boolean
|
|
|
582
605
|
|
|
583
606
|
# (Required) List of provisioning steps
|
|
584
607
|
steps:
|
|
585
|
-
- name: string
|
|
586
|
-
description: string
|
|
587
|
-
command: string
|
|
588
|
-
skip_if: string
|
|
608
|
+
- name: string # (Required) Step name
|
|
609
|
+
description: string # (Optional) Step description
|
|
610
|
+
command: string # (Required) Shell command to execute
|
|
611
|
+
skip_if: string # (Optional) Skip if this command succeeds
|
|
589
612
|
continue_on_error: boolean # (Optional) Continue on failure (default: false)
|
|
590
613
|
```
|
|
591
614
|
|
|
@@ -671,6 +694,7 @@ After initialization, syncpoint creates the following structure:
|
|
|
671
694
|
### Backup Archive Contents
|
|
672
695
|
|
|
673
696
|
Each backup archive contains:
|
|
697
|
+
|
|
674
698
|
- Your configuration files in their relative paths
|
|
675
699
|
- `_metadata.json` with backup information:
|
|
676
700
|
- File hashes for comparison
|
|
@@ -810,6 +834,7 @@ Syncpoint includes multiple security layers to protect your data and system:
|
|
|
810
834
|
**Claude Code CLI not found**
|
|
811
835
|
|
|
812
836
|
If you see "Claude Code CLI not found" error:
|
|
837
|
+
|
|
813
838
|
1. Install Claude Code CLI: Visit [claude.ai/code](https://claude.ai/code) for installation instructions
|
|
814
839
|
2. Or use `--print` mode to get the prompt and use it with your preferred LLM
|
|
815
840
|
3. Verify installation: `claude --version`
|
|
@@ -817,6 +842,7 @@ If you see "Claude Code CLI not found" error:
|
|
|
817
842
|
**Validation errors after LLM generation**
|
|
818
843
|
|
|
819
844
|
The wizard automatically retries up to 3 times when validation fails:
|
|
845
|
+
|
|
820
846
|
- Each retry includes error feedback to help the LLM correct the issues
|
|
821
847
|
- If all retries fail, check the validation error messages
|
|
822
848
|
- Common issues:
|
|
@@ -827,6 +853,7 @@ The wizard automatically retries up to 3 times when validation fails:
|
|
|
827
853
|
**Print mode usage**
|
|
828
854
|
|
|
829
855
|
Use `--print` mode when Claude Code is not available:
|
|
856
|
+
|
|
830
857
|
```bash
|
|
831
858
|
# Get the prompt
|
|
832
859
|
npx @lumy-pack/syncpoint wizard --print > prompt.txt
|
|
@@ -838,6 +865,7 @@ npx @lumy-pack/syncpoint wizard --print > prompt.txt
|
|
|
838
865
|
**Session context lost**
|
|
839
866
|
|
|
840
867
|
The wizard preserves session context across retries using Claude Code's session management. If context is lost:
|
|
868
|
+
|
|
841
869
|
- The wizard will start a new session on the next retry
|
|
842
870
|
- Manual intervention may be needed after 3 failed attempts
|
|
843
871
|
|
|
@@ -846,6 +874,7 @@ The wizard preserves session context across retries using Claude Code's session
|
|
|
846
874
|
**Permission errors**
|
|
847
875
|
|
|
848
876
|
If you encounter permission errors:
|
|
877
|
+
|
|
849
878
|
- Ensure you have write access to `~/.syncpoint/`
|
|
850
879
|
- Check file permissions: `ls -la ~/.syncpoint/`
|
|
851
880
|
- Run with appropriate permissions (avoid unnecessary sudo)
|
|
@@ -853,6 +882,7 @@ If you encounter permission errors:
|
|
|
853
882
|
**Large file warnings**
|
|
854
883
|
|
|
855
884
|
Files larger than 10MB trigger warnings:
|
|
885
|
+
|
|
856
886
|
- Consider excluding large files using `exclude` patterns
|
|
857
887
|
- Review if these files should be in version control instead
|
|
858
888
|
- Compress large files before backing up
|
|
@@ -860,6 +890,7 @@ Files larger than 10MB trigger warnings:
|
|
|
860
890
|
**Backup restore conflicts**
|
|
861
891
|
|
|
862
892
|
If restore shows many "overwrite" actions:
|
|
893
|
+
|
|
863
894
|
- Use `--dry-run` to preview changes first
|
|
864
895
|
- Automatic safety backup is created before overwrite
|
|
865
896
|
- Review the safety backup in `~/.syncpoint/backups/` tagged with `_pre-restore_`
|
|
@@ -3,18 +3,12 @@
|
|
|
3
3
|
"title": "Syncpoint Config",
|
|
4
4
|
"description": "Configuration for syncpoint backup tool",
|
|
5
5
|
"type": "object",
|
|
6
|
-
"required": [
|
|
7
|
-
"backup"
|
|
8
|
-
],
|
|
6
|
+
"required": ["backup"],
|
|
9
7
|
"properties": {
|
|
10
8
|
"backup": {
|
|
11
9
|
"type": "object",
|
|
12
10
|
"description": "Backup configuration",
|
|
13
|
-
"required": [
|
|
14
|
-
"targets",
|
|
15
|
-
"exclude",
|
|
16
|
-
"filename"
|
|
17
|
-
],
|
|
11
|
+
"required": ["targets", "exclude", "filename"],
|
|
18
12
|
"properties": {
|
|
19
13
|
"targets": {
|
|
20
14
|
"type": "array",
|
|
@@ -65,4 +59,4 @@
|
|
|
65
59
|
}
|
|
66
60
|
},
|
|
67
61
|
"additionalProperties": false
|
|
68
|
-
}
|
|
62
|
+
}
|
package/dist/cli.mjs
CHANGED
|
@@ -843,18 +843,12 @@ var config_schema_default = {
|
|
|
843
843
|
title: "Syncpoint Config",
|
|
844
844
|
description: "Configuration for syncpoint backup tool",
|
|
845
845
|
type: "object",
|
|
846
|
-
required: [
|
|
847
|
-
"backup"
|
|
848
|
-
],
|
|
846
|
+
required: ["backup"],
|
|
849
847
|
properties: {
|
|
850
848
|
backup: {
|
|
851
849
|
type: "object",
|
|
852
850
|
description: "Backup configuration",
|
|
853
|
-
required: [
|
|
854
|
-
"targets",
|
|
855
|
-
"exclude",
|
|
856
|
-
"filename"
|
|
857
|
-
],
|
|
851
|
+
required: ["targets", "exclude", "filename"],
|
|
858
852
|
properties: {
|
|
859
853
|
targets: {
|
|
860
854
|
type: "array",
|
|
@@ -1210,7 +1204,11 @@ var BackupView = ({ options }) => {
|
|
|
1210
1204
|
for (const f of foundFiles) {
|
|
1211
1205
|
if (seen.has(f.absolutePath)) continue;
|
|
1212
1206
|
seen.add(f.absolutePath);
|
|
1213
|
-
deduped.push({
|
|
1207
|
+
deduped.push({
|
|
1208
|
+
id: `found-${f.absolutePath}`,
|
|
1209
|
+
type: "found",
|
|
1210
|
+
file: f
|
|
1211
|
+
});
|
|
1214
1212
|
}
|
|
1215
1213
|
for (const p of missingFiles) {
|
|
1216
1214
|
if (seen.has(p)) continue;
|
|
@@ -1334,21 +1332,72 @@ function registerBackupCommand(program2) {
|
|
|
1334
1332
|
cmdInfo.options?.forEach((opt) => {
|
|
1335
1333
|
cmd.option(opt.flag, opt.description);
|
|
1336
1334
|
});
|
|
1337
|
-
cmd.action(
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1335
|
+
cmd.action(
|
|
1336
|
+
async (opts) => {
|
|
1337
|
+
const { waitUntilExit } = render(
|
|
1338
|
+
/* @__PURE__ */ jsx2(
|
|
1339
|
+
BackupView,
|
|
1340
|
+
{
|
|
1341
|
+
options: {
|
|
1342
|
+
dryRun: opts.dryRun,
|
|
1343
|
+
tag: opts.tag,
|
|
1344
|
+
verbose: opts.verbose
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
)
|
|
1348
|
+
);
|
|
1349
|
+
await waitUntilExit();
|
|
1350
|
+
}
|
|
1351
|
+
);
|
|
1343
1352
|
}
|
|
1344
1353
|
|
|
1345
1354
|
// src/commands/CreateTemplate.tsx
|
|
1346
|
-
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
1347
|
-
import { Text as Text3, Box as Box2, useApp as useApp2 } from "ink";
|
|
1348
|
-
import Spinner from "ink-spinner";
|
|
1349
|
-
import { render as render2 } from "ink";
|
|
1350
|
-
import { join as join8 } from "path";
|
|
1351
1355
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
1356
|
+
import { join as join8 } from "path";
|
|
1357
|
+
import { Box as Box2, Text as Text3, useApp as useApp2 } from "ink";
|
|
1358
|
+
import { render as render2 } from "ink";
|
|
1359
|
+
import Spinner from "ink-spinner";
|
|
1360
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1361
|
+
|
|
1362
|
+
// src/prompts/wizard-template.ts
|
|
1363
|
+
function generateTemplateWizardPrompt(variables) {
|
|
1364
|
+
return `You are a Syncpoint provisioning template assistant. Your role is to help users create automated environment setup templates.
|
|
1365
|
+
|
|
1366
|
+
**Input:**
|
|
1367
|
+
1. User's provisioning requirements (described in natural language)
|
|
1368
|
+
2. Example template structure (YAML)
|
|
1369
|
+
|
|
1370
|
+
**Your Task:**
|
|
1371
|
+
1. Ask clarifying questions to understand the provisioning workflow:
|
|
1372
|
+
- What software/tools need to be installed?
|
|
1373
|
+
- What dependencies should be checked?
|
|
1374
|
+
- Are there any configuration steps after installation?
|
|
1375
|
+
- Should any steps require sudo privileges?
|
|
1376
|
+
- Should any steps be conditional (skip_if)?
|
|
1377
|
+
2. Based on user responses, generate a complete provision template
|
|
1378
|
+
|
|
1379
|
+
**Output Requirements:**
|
|
1380
|
+
- Pure YAML format only (no markdown, no code blocks, no explanations)
|
|
1381
|
+
- Must be valid according to Syncpoint template schema
|
|
1382
|
+
- Required fields:
|
|
1383
|
+
- \`name\`: Template name
|
|
1384
|
+
- \`steps\`: Array of provisioning steps (minimum 1)
|
|
1385
|
+
- Each step must include:
|
|
1386
|
+
- \`name\`: Step name (required)
|
|
1387
|
+
- \`command\`: Shell command to execute (required)
|
|
1388
|
+
- \`description\`: Step description (optional)
|
|
1389
|
+
- \`skip_if\`: Condition to skip step (optional)
|
|
1390
|
+
- \`continue_on_error\`: Whether to continue on failure (optional, default: false)
|
|
1391
|
+
- Optional template fields:
|
|
1392
|
+
- \`description\`: Template description
|
|
1393
|
+
- \`backup\`: Backup name to restore after provisioning
|
|
1394
|
+
- \`sudo\`: Whether sudo is required (boolean)
|
|
1395
|
+
|
|
1396
|
+
**Example Template:**
|
|
1397
|
+
${variables.exampleTemplate}
|
|
1398
|
+
|
|
1399
|
+
Begin by asking the user to describe their provisioning needs.`;
|
|
1400
|
+
}
|
|
1352
1401
|
|
|
1353
1402
|
// assets/schemas/template.schema.json
|
|
1354
1403
|
var template_schema_default = {
|
|
@@ -1424,46 +1473,6 @@ function validateTemplate(data) {
|
|
|
1424
1473
|
return { valid: false, errors };
|
|
1425
1474
|
}
|
|
1426
1475
|
|
|
1427
|
-
// src/prompts/wizard-template.ts
|
|
1428
|
-
function generateTemplateWizardPrompt(variables) {
|
|
1429
|
-
return `You are a Syncpoint provisioning template assistant. Your role is to help users create automated environment setup templates.
|
|
1430
|
-
|
|
1431
|
-
**Input:**
|
|
1432
|
-
1. User's provisioning requirements (described in natural language)
|
|
1433
|
-
2. Example template structure (YAML)
|
|
1434
|
-
|
|
1435
|
-
**Your Task:**
|
|
1436
|
-
1. Ask clarifying questions to understand the provisioning workflow:
|
|
1437
|
-
- What software/tools need to be installed?
|
|
1438
|
-
- What dependencies should be checked?
|
|
1439
|
-
- Are there any configuration steps after installation?
|
|
1440
|
-
- Should any steps require sudo privileges?
|
|
1441
|
-
- Should any steps be conditional (skip_if)?
|
|
1442
|
-
2. Based on user responses, generate a complete provision template
|
|
1443
|
-
|
|
1444
|
-
**Output Requirements:**
|
|
1445
|
-
- Pure YAML format only (no markdown, no code blocks, no explanations)
|
|
1446
|
-
- Must be valid according to Syncpoint template schema
|
|
1447
|
-
- Required fields:
|
|
1448
|
-
- \`name\`: Template name
|
|
1449
|
-
- \`steps\`: Array of provisioning steps (minimum 1)
|
|
1450
|
-
- Each step must include:
|
|
1451
|
-
- \`name\`: Step name (required)
|
|
1452
|
-
- \`command\`: Shell command to execute (required)
|
|
1453
|
-
- \`description\`: Step description (optional)
|
|
1454
|
-
- \`skip_if\`: Condition to skip step (optional)
|
|
1455
|
-
- \`continue_on_error\`: Whether to continue on failure (optional, default: false)
|
|
1456
|
-
- Optional template fields:
|
|
1457
|
-
- \`description\`: Template description
|
|
1458
|
-
- \`backup\`: Backup name to restore after provisioning
|
|
1459
|
-
- \`sudo\`: Whether sudo is required (boolean)
|
|
1460
|
-
|
|
1461
|
-
**Example Template:**
|
|
1462
|
-
${variables.exampleTemplate}
|
|
1463
|
-
|
|
1464
|
-
Begin by asking the user to describe their provisioning needs.`;
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
1476
|
// src/utils/claude-code-runner.ts
|
|
1468
1477
|
import { spawn } from "child_process";
|
|
1469
1478
|
async function isClaudeCodeAvailable() {
|
|
@@ -1566,6 +1575,39 @@ Start by asking the user about their backup priorities for the home directory st
|
|
|
1566
1575
|
});
|
|
1567
1576
|
}
|
|
1568
1577
|
|
|
1578
|
+
// src/utils/error-formatter.ts
|
|
1579
|
+
function formatValidationErrors(errors) {
|
|
1580
|
+
if (errors.length === 0) {
|
|
1581
|
+
return "No validation errors.";
|
|
1582
|
+
}
|
|
1583
|
+
const formattedErrors = errors.map((error, index) => {
|
|
1584
|
+
return `${index + 1}. ${error}`;
|
|
1585
|
+
});
|
|
1586
|
+
return `Validation failed with ${errors.length} error(s):
|
|
1587
|
+
|
|
1588
|
+
${formattedErrors.join("\n")}`;
|
|
1589
|
+
}
|
|
1590
|
+
function createRetryPrompt(originalPrompt, errors, attemptNumber) {
|
|
1591
|
+
const errorSummary = formatValidationErrors(errors);
|
|
1592
|
+
return `${originalPrompt}
|
|
1593
|
+
|
|
1594
|
+
---
|
|
1595
|
+
|
|
1596
|
+
**VALIDATION FAILED (Attempt ${attemptNumber})**
|
|
1597
|
+
|
|
1598
|
+
The previously generated YAML configuration did not pass validation:
|
|
1599
|
+
|
|
1600
|
+
${errorSummary}
|
|
1601
|
+
|
|
1602
|
+
Please analyze these errors and generate a corrected YAML configuration that addresses all validation issues.
|
|
1603
|
+
|
|
1604
|
+
Remember:
|
|
1605
|
+
- Output pure YAML only (no markdown, no code blocks, no explanations)
|
|
1606
|
+
- Ensure all required fields are present
|
|
1607
|
+
- Follow the correct schema structure
|
|
1608
|
+
- Validate pattern syntax for targets and exclude arrays`;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1569
1611
|
// src/utils/yaml-parser.ts
|
|
1570
1612
|
import YAML2 from "yaml";
|
|
1571
1613
|
function isStructuredYAML(parsed) {
|
|
@@ -1603,39 +1645,6 @@ function parseYAML(yamlString) {
|
|
|
1603
1645
|
return YAML2.parse(yamlString);
|
|
1604
1646
|
}
|
|
1605
1647
|
|
|
1606
|
-
// src/utils/error-formatter.ts
|
|
1607
|
-
function formatValidationErrors(errors) {
|
|
1608
|
-
if (errors.length === 0) {
|
|
1609
|
-
return "No validation errors.";
|
|
1610
|
-
}
|
|
1611
|
-
const formattedErrors = errors.map((error, index) => {
|
|
1612
|
-
return `${index + 1}. ${error}`;
|
|
1613
|
-
});
|
|
1614
|
-
return `Validation failed with ${errors.length} error(s):
|
|
1615
|
-
|
|
1616
|
-
${formattedErrors.join("\n")}`;
|
|
1617
|
-
}
|
|
1618
|
-
function createRetryPrompt(originalPrompt, errors, attemptNumber) {
|
|
1619
|
-
const errorSummary = formatValidationErrors(errors);
|
|
1620
|
-
return `${originalPrompt}
|
|
1621
|
-
|
|
1622
|
-
---
|
|
1623
|
-
|
|
1624
|
-
**VALIDATION FAILED (Attempt ${attemptNumber})**
|
|
1625
|
-
|
|
1626
|
-
The previously generated YAML configuration did not pass validation:
|
|
1627
|
-
|
|
1628
|
-
${errorSummary}
|
|
1629
|
-
|
|
1630
|
-
Please analyze these errors and generate a corrected YAML configuration that addresses all validation issues.
|
|
1631
|
-
|
|
1632
|
-
Remember:
|
|
1633
|
-
- Output pure YAML only (no markdown, no code blocks, no explanations)
|
|
1634
|
-
- Ensure all required fields are present
|
|
1635
|
-
- Follow the correct schema structure
|
|
1636
|
-
- Validate pattern syntax for targets and exclude arrays`;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
1648
|
// src/commands/CreateTemplate.tsx
|
|
1640
1649
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1641
1650
|
var MAX_RETRIES = 3;
|
|
@@ -1685,7 +1694,9 @@ var CreateTemplateView = ({
|
|
|
1685
1694
|
while (currentAttempt <= MAX_RETRIES) {
|
|
1686
1695
|
try {
|
|
1687
1696
|
setPhase("llm-invoke");
|
|
1688
|
-
setMessage(
|
|
1697
|
+
setMessage(
|
|
1698
|
+
`Generating template... (Attempt ${currentAttempt}/${MAX_RETRIES})`
|
|
1699
|
+
);
|
|
1689
1700
|
const result = currentSessionId ? await resumeClaudeCodeSession(currentSessionId, currentPrompt) : await invokeClaudeCode(currentPrompt);
|
|
1690
1701
|
if (!result.success) {
|
|
1691
1702
|
throw new Error(result.error || "Failed to invoke Claude Code");
|
|
@@ -1762,8 +1773,11 @@ ${formatValidationErrors(validation.errors || [])}`
|
|
|
1762
1773
|
/* @__PURE__ */ jsx3(Text3, { color: "green", children: message }),
|
|
1763
1774
|
/* @__PURE__ */ jsxs3(Box2, { marginTop: 1, children: [
|
|
1764
1775
|
/* @__PURE__ */ jsx3(Text3, { children: "Next steps:" }),
|
|
1765
|
-
/* @__PURE__ */ jsx3(Text3, { children: "
|
|
1766
|
-
/* @__PURE__ */
|
|
1776
|
+
/* @__PURE__ */ jsx3(Text3, { children: " 1. Review your template: syncpoint list templates" }),
|
|
1777
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1778
|
+
" ",
|
|
1779
|
+
"2. Run provisioning: syncpoint provision <template-name>"
|
|
1780
|
+
] })
|
|
1767
1781
|
] })
|
|
1768
1782
|
] });
|
|
1769
1783
|
}
|
|
@@ -1784,7 +1798,13 @@ ${formatValidationErrors(validation.errors || [])}`
|
|
|
1784
1798
|
function registerCreateTemplateCommand(program2) {
|
|
1785
1799
|
program2.command("create-template [name]").description("Interactive wizard to create a provisioning template").option("-p, --print", "Print prompt instead of invoking Claude Code").action(async (name, opts) => {
|
|
1786
1800
|
const { waitUntilExit } = render2(
|
|
1787
|
-
/* @__PURE__ */ jsx3(
|
|
1801
|
+
/* @__PURE__ */ jsx3(
|
|
1802
|
+
CreateTemplateView,
|
|
1803
|
+
{
|
|
1804
|
+
printMode: opts.print || false,
|
|
1805
|
+
templateName: name
|
|
1806
|
+
}
|
|
1807
|
+
)
|
|
1788
1808
|
);
|
|
1789
1809
|
await waitUntilExit();
|
|
1790
1810
|
});
|
|
@@ -1935,10 +1955,10 @@ function registerHelpCommand(program2) {
|
|
|
1935
1955
|
}
|
|
1936
1956
|
|
|
1937
1957
|
// src/commands/Init.tsx
|
|
1938
|
-
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
1939
|
-
import { Text as Text5, Box as Box4, useApp as useApp3 } from "ink";
|
|
1940
|
-
import { render as render4 } from "ink";
|
|
1941
1958
|
import { join as join9 } from "path";
|
|
1959
|
+
import { Box as Box4, Text as Text5, useApp as useApp3 } from "ink";
|
|
1960
|
+
import { render as render4 } from "ink";
|
|
1961
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
1942
1962
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1943
1963
|
var InitView = () => {
|
|
1944
1964
|
const { exit } = useApp3();
|
|
@@ -1980,9 +2000,15 @@ var InitView = () => {
|
|
|
1980
2000
|
setSteps([...completed]);
|
|
1981
2001
|
}
|
|
1982
2002
|
await initDefaultConfig();
|
|
1983
|
-
completed.push({
|
|
2003
|
+
completed.push({
|
|
2004
|
+
name: `Created ${CONFIG_FILENAME} (defaults)`,
|
|
2005
|
+
done: true
|
|
2006
|
+
});
|
|
1984
2007
|
setSteps([...completed]);
|
|
1985
|
-
const exampleTemplatePath = join9(
|
|
2008
|
+
const exampleTemplatePath = join9(
|
|
2009
|
+
getSubDir(TEMPLATES_DIR),
|
|
2010
|
+
"example.yml"
|
|
2011
|
+
);
|
|
1986
2012
|
if (!await fileExists(exampleTemplatePath)) {
|
|
1987
2013
|
const { writeFile: writeFile6 } = await import("fs/promises");
|
|
1988
2014
|
const exampleYaml = readAsset("template.example.yml");
|
|
@@ -2033,7 +2059,9 @@ var InitView = () => {
|
|
|
2033
2059
|
] });
|
|
2034
2060
|
};
|
|
2035
2061
|
function registerInitCommand(program2) {
|
|
2036
|
-
program2.command("init").description(
|
|
2062
|
+
program2.command("init").description(
|
|
2063
|
+
`Initialize ~/.${APP_NAME}/ directory structure and default config`
|
|
2064
|
+
).action(async () => {
|
|
2037
2065
|
const { waitUntilExit } = render4(/* @__PURE__ */ jsx5(InitView, {}));
|
|
2038
2066
|
await waitUntilExit();
|
|
2039
2067
|
});
|
|
@@ -2084,7 +2112,7 @@ var Confirm = ({
|
|
|
2084
2112
|
};
|
|
2085
2113
|
|
|
2086
2114
|
// src/components/Table.tsx
|
|
2087
|
-
import {
|
|
2115
|
+
import { Box as Box5, Text as Text7 } from "ink";
|
|
2088
2116
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2089
2117
|
var Table = ({
|
|
2090
2118
|
headers,
|
|
@@ -2555,7 +2583,9 @@ var ListView = ({ type, deleteIndex }) => {
|
|
|
2555
2583
|
if (yes && deleteTarget) {
|
|
2556
2584
|
try {
|
|
2557
2585
|
if (!isInsideDir(deleteTarget.path, backupDir)) {
|
|
2558
|
-
throw new Error(
|
|
2586
|
+
throw new Error(
|
|
2587
|
+
`Refusing to delete file outside backups directory: ${deleteTarget.path}`
|
|
2588
|
+
);
|
|
2559
2589
|
}
|
|
2560
2590
|
unlinkSync(deleteTarget.path);
|
|
2561
2591
|
const deletedName = deleteTarget.name;
|
|
@@ -2828,9 +2858,9 @@ function registerListCommand(program2) {
|
|
|
2828
2858
|
}
|
|
2829
2859
|
|
|
2830
2860
|
// src/commands/Migrate.tsx
|
|
2831
|
-
import {
|
|
2832
|
-
import { Text as Text9, Box as Box7, useApp as useApp5 } from "ink";
|
|
2861
|
+
import { Box as Box7, Text as Text9, useApp as useApp5 } from "ink";
|
|
2833
2862
|
import { render as render6 } from "ink";
|
|
2863
|
+
import { useEffect as useEffect5, useState as useState6 } from "react";
|
|
2834
2864
|
|
|
2835
2865
|
// src/core/migrate.ts
|
|
2836
2866
|
import { copyFile as copyFile2, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -3063,44 +3093,12 @@ function registerMigrateCommand(program2) {
|
|
|
3063
3093
|
}
|
|
3064
3094
|
|
|
3065
3095
|
// src/commands/Provision.tsx
|
|
3066
|
-
import {
|
|
3067
|
-
import { Text as Text11, Box as Box9, useApp as useApp6 } from "ink";
|
|
3096
|
+
import { Box as Box9, Text as Text11, useApp as useApp6 } from "ink";
|
|
3068
3097
|
import { render as render7 } from "ink";
|
|
3069
|
-
|
|
3070
|
-
// src/utils/sudo.ts
|
|
3071
|
-
import { execSync } from "child_process";
|
|
3072
|
-
import pc2 from "picocolors";
|
|
3073
|
-
function isSudoCached() {
|
|
3074
|
-
try {
|
|
3075
|
-
execSync("sudo -n true", { stdio: "ignore" });
|
|
3076
|
-
return true;
|
|
3077
|
-
} catch {
|
|
3078
|
-
return false;
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
function ensureSudo(templateName) {
|
|
3082
|
-
if (isSudoCached()) return;
|
|
3083
|
-
console.log(
|
|
3084
|
-
`
|
|
3085
|
-
${pc2.yellow("\u26A0")} Template ${pc2.bold(templateName)} requires ${pc2.bold("sudo")} privileges.`
|
|
3086
|
-
);
|
|
3087
|
-
console.log(
|
|
3088
|
-
pc2.gray(" Some provisioning steps need elevated permissions to execute.")
|
|
3089
|
-
);
|
|
3090
|
-
console.log(pc2.gray(" You will be prompted for your password.\n"));
|
|
3091
|
-
try {
|
|
3092
|
-
execSync("sudo -v", { stdio: "inherit", timeout: 6e4 });
|
|
3093
|
-
} catch {
|
|
3094
|
-
console.error(
|
|
3095
|
-
`
|
|
3096
|
-
${pc2.red("\u2717")} Sudo authentication failed or was cancelled. Aborting.`
|
|
3097
|
-
);
|
|
3098
|
-
process.exit(1);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3098
|
+
import { useEffect as useEffect6, useState as useState7 } from "react";
|
|
3101
3099
|
|
|
3102
3100
|
// src/components/StepRunner.tsx
|
|
3103
|
-
import {
|
|
3101
|
+
import { Box as Box8, Static as Static2, Text as Text10 } from "ink";
|
|
3104
3102
|
import Spinner2 from "ink-spinner";
|
|
3105
3103
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3106
3104
|
var StepIcon = ({ status }) => {
|
|
@@ -3169,10 +3167,7 @@ var StepItemView = ({
|
|
|
3169
3167
|
/* @__PURE__ */ jsx10(StepStatusText, { step })
|
|
3170
3168
|
] })
|
|
3171
3169
|
] });
|
|
3172
|
-
var StepRunner = ({
|
|
3173
|
-
steps,
|
|
3174
|
-
total
|
|
3175
|
-
}) => {
|
|
3170
|
+
var StepRunner = ({ steps, total }) => {
|
|
3176
3171
|
const completedSteps = [];
|
|
3177
3172
|
const activeSteps = [];
|
|
3178
3173
|
steps.forEach((step, idx) => {
|
|
@@ -3208,6 +3203,38 @@ var StepRunner = ({
|
|
|
3208
3203
|
] });
|
|
3209
3204
|
};
|
|
3210
3205
|
|
|
3206
|
+
// src/utils/sudo.ts
|
|
3207
|
+
import { execSync } from "child_process";
|
|
3208
|
+
import pc2 from "picocolors";
|
|
3209
|
+
function isSudoCached() {
|
|
3210
|
+
try {
|
|
3211
|
+
execSync("sudo -n true", { stdio: "ignore" });
|
|
3212
|
+
return true;
|
|
3213
|
+
} catch {
|
|
3214
|
+
return false;
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
function ensureSudo(templateName) {
|
|
3218
|
+
if (isSudoCached()) return;
|
|
3219
|
+
console.log(
|
|
3220
|
+
`
|
|
3221
|
+
${pc2.yellow("\u26A0")} Template ${pc2.bold(templateName)} requires ${pc2.bold("sudo")} privileges.`
|
|
3222
|
+
);
|
|
3223
|
+
console.log(
|
|
3224
|
+
pc2.gray(" Some provisioning steps need elevated permissions to execute.")
|
|
3225
|
+
);
|
|
3226
|
+
console.log(pc2.gray(" You will be prompted for your password.\n"));
|
|
3227
|
+
try {
|
|
3228
|
+
execSync("sudo -v", { stdio: "inherit", timeout: 6e4 });
|
|
3229
|
+
} catch {
|
|
3230
|
+
console.error(
|
|
3231
|
+
`
|
|
3232
|
+
${pc2.red("\u2717")} Sudo authentication failed or was cancelled. Aborting.`
|
|
3233
|
+
);
|
|
3234
|
+
process.exit(1);
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3211
3238
|
// src/commands/Provision.tsx
|
|
3212
3239
|
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3213
3240
|
var ProvisionView = ({
|
|
@@ -3216,7 +3243,9 @@ var ProvisionView = ({
|
|
|
3216
3243
|
options
|
|
3217
3244
|
}) => {
|
|
3218
3245
|
const { exit } = useApp6();
|
|
3219
|
-
const [phase, setPhase] = useState7(
|
|
3246
|
+
const [phase, setPhase] = useState7(
|
|
3247
|
+
options.dryRun ? "done" : "running"
|
|
3248
|
+
);
|
|
3220
3249
|
const [steps, setSteps] = useState7(
|
|
3221
3250
|
template.steps.map((s) => ({
|
|
3222
3251
|
name: s.name,
|
|
@@ -3361,7 +3390,11 @@ var ProvisionView = ({
|
|
|
3361
3390
|
] });
|
|
3362
3391
|
};
|
|
3363
3392
|
function registerProvisionCommand(program2) {
|
|
3364
|
-
program2.command("provision [template]").description("Run template-based machine provisioning").option("--dry-run", "Show plan without execution", false).option(
|
|
3393
|
+
program2.command("provision [template]").description("Run template-based machine provisioning").option("--dry-run", "Show plan without execution", false).option(
|
|
3394
|
+
"--skip-restore",
|
|
3395
|
+
"Skip automatic restore after template completion",
|
|
3396
|
+
false
|
|
3397
|
+
).option("-f, --file <path>", "Path to template file").action(
|
|
3365
3398
|
async (templateName, opts) => {
|
|
3366
3399
|
let templatePath;
|
|
3367
3400
|
if (opts.file) {
|
|
@@ -3371,7 +3404,9 @@ function registerProvisionCommand(program2) {
|
|
|
3371
3404
|
process.exit(1);
|
|
3372
3405
|
}
|
|
3373
3406
|
if (!templatePath.endsWith(".yml") && !templatePath.endsWith(".yaml")) {
|
|
3374
|
-
console.error(
|
|
3407
|
+
console.error(
|
|
3408
|
+
`Template file must have .yml or .yaml extension: ${opts.file}`
|
|
3409
|
+
);
|
|
3375
3410
|
process.exit(1);
|
|
3376
3411
|
}
|
|
3377
3412
|
} else if (templateName) {
|
|
@@ -3385,7 +3420,9 @@ function registerProvisionCommand(program2) {
|
|
|
3385
3420
|
}
|
|
3386
3421
|
templatePath = match.path;
|
|
3387
3422
|
} else {
|
|
3388
|
-
console.error(
|
|
3423
|
+
console.error(
|
|
3424
|
+
"Error: Either <template> name or --file option must be provided"
|
|
3425
|
+
);
|
|
3389
3426
|
console.error("Usage: syncpoint provision <template> [options]");
|
|
3390
3427
|
console.error(" syncpoint provision --file <path> [options]");
|
|
3391
3428
|
process.exit(1);
|
|
@@ -3413,10 +3450,10 @@ function registerProvisionCommand(program2) {
|
|
|
3413
3450
|
}
|
|
3414
3451
|
|
|
3415
3452
|
// src/commands/Restore.tsx
|
|
3416
|
-
import {
|
|
3417
|
-
import { Text as Text12, Box as Box10, useApp as useApp7 } from "ink";
|
|
3418
|
-
import SelectInput2 from "ink-select-input";
|
|
3453
|
+
import { Box as Box10, Text as Text12, useApp as useApp7 } from "ink";
|
|
3419
3454
|
import { render as render8 } from "ink";
|
|
3455
|
+
import SelectInput2 from "ink-select-input";
|
|
3456
|
+
import { useEffect as useEffect7, useState as useState8 } from "react";
|
|
3420
3457
|
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3421
3458
|
var RestoreView = ({ filename, options }) => {
|
|
3422
3459
|
const { exit } = useApp7();
|
|
@@ -3630,13 +3667,7 @@ var RestoreView = ({ filename, options }) => {
|
|
|
3630
3667
|
function registerRestoreCommand(program2) {
|
|
3631
3668
|
program2.command("restore [filename]").description("Restore config files from a backup").option("--dry-run", "Show planned changes without actual restore", false).action(async (filename, opts) => {
|
|
3632
3669
|
const { waitUntilExit } = render8(
|
|
3633
|
-
/* @__PURE__ */ jsx12(
|
|
3634
|
-
RestoreView,
|
|
3635
|
-
{
|
|
3636
|
-
filename,
|
|
3637
|
-
options: { dryRun: opts.dryRun }
|
|
3638
|
-
}
|
|
3639
|
-
)
|
|
3670
|
+
/* @__PURE__ */ jsx12(RestoreView, { filename, options: { dryRun: opts.dryRun } })
|
|
3640
3671
|
);
|
|
3641
3672
|
await waitUntilExit();
|
|
3642
3673
|
});
|
|
@@ -3787,7 +3818,10 @@ var StatusView = ({ cleanup }) => {
|
|
|
3787
3818
|
if (cleanupAction === "keep-recent-5") {
|
|
3788
3819
|
const toDelete = backups.slice(5);
|
|
3789
3820
|
for (const b of toDelete) {
|
|
3790
|
-
if (!isInsideDir(b.path, backupDir))
|
|
3821
|
+
if (!isInsideDir(b.path, backupDir))
|
|
3822
|
+
throw new Error(
|
|
3823
|
+
`Refusing to delete file outside backups directory: ${b.path}`
|
|
3824
|
+
);
|
|
3791
3825
|
unlinkSync2(b.path);
|
|
3792
3826
|
}
|
|
3793
3827
|
} else if (cleanupAction === "older-than-30") {
|
|
@@ -3795,7 +3829,10 @@ var StatusView = ({ cleanup }) => {
|
|
|
3795
3829
|
cutoff.setDate(cutoff.getDate() - 30);
|
|
3796
3830
|
const toDelete = backups.filter((b) => b.createdAt < cutoff);
|
|
3797
3831
|
for (const b of toDelete) {
|
|
3798
|
-
if (!isInsideDir(b.path, backupDir))
|
|
3832
|
+
if (!isInsideDir(b.path, backupDir))
|
|
3833
|
+
throw new Error(
|
|
3834
|
+
`Refusing to delete file outside backups directory: ${b.path}`
|
|
3835
|
+
);
|
|
3799
3836
|
unlinkSync2(b.path);
|
|
3800
3837
|
}
|
|
3801
3838
|
} else if (cleanupAction === "delete-logs") {
|
|
@@ -3804,7 +3841,10 @@ var StatusView = ({ cleanup }) => {
|
|
|
3804
3841
|
const entries = readdirSync(logsDir);
|
|
3805
3842
|
for (const entry of entries) {
|
|
3806
3843
|
const logPath = join12(logsDir, entry);
|
|
3807
|
-
if (!isInsideDir(logPath, logsDir))
|
|
3844
|
+
if (!isInsideDir(logPath, logsDir))
|
|
3845
|
+
throw new Error(
|
|
3846
|
+
`Refusing to delete file outside logs directory: ${logPath}`
|
|
3847
|
+
);
|
|
3808
3848
|
try {
|
|
3809
3849
|
if (statSync(logPath).isFile()) {
|
|
3810
3850
|
unlinkSync2(logPath);
|
|
@@ -3816,7 +3856,10 @@ var StatusView = ({ cleanup }) => {
|
|
|
3816
3856
|
}
|
|
3817
3857
|
} else if (cleanupAction === "select-specific") {
|
|
3818
3858
|
for (const b of selectedForDeletion) {
|
|
3819
|
-
if (!isInsideDir(b.path, backupDir))
|
|
3859
|
+
if (!isInsideDir(b.path, backupDir))
|
|
3860
|
+
throw new Error(
|
|
3861
|
+
`Refusing to delete file outside backups directory: ${b.path}`
|
|
3862
|
+
);
|
|
3820
3863
|
unlinkSync2(b.path);
|
|
3821
3864
|
}
|
|
3822
3865
|
}
|
|
@@ -4043,7 +4086,13 @@ function registerStatusCommand(program2) {
|
|
|
4043
4086
|
}
|
|
4044
4087
|
|
|
4045
4088
|
// src/commands/Wizard.tsx
|
|
4046
|
-
import {
|
|
4089
|
+
import {
|
|
4090
|
+
copyFile as copyFile3,
|
|
4091
|
+
readFile as readFile6,
|
|
4092
|
+
rename,
|
|
4093
|
+
unlink,
|
|
4094
|
+
writeFile as writeFile5
|
|
4095
|
+
} from "fs/promises";
|
|
4047
4096
|
import { join as join14 } from "path";
|
|
4048
4097
|
import { Box as Box12, Text as Text14, useApp as useApp9 } from "ink";
|
|
4049
4098
|
import { render as render10 } from "ink";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Command } from
|
|
1
|
+
import { Command } from 'commander';
|
|
2
2
|
export declare function registerCreateTemplateCommand(program: Command): void;
|
package/dist/commands/Init.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Command } from
|
|
1
|
+
import { Command } from 'commander';
|
|
2
2
|
export declare function registerInitCommand(program: Command): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Command } from
|
|
1
|
+
import { Command } from 'commander';
|
|
2
2
|
export declare function registerProvisionCommand(program: Command): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Command } from
|
|
1
|
+
import { Command } from 'commander';
|
|
2
2
|
export declare function registerRestoreCommand(program: Command): void;
|
package/dist/index.cjs
CHANGED
|
@@ -132,18 +132,12 @@ var config_schema_default = {
|
|
|
132
132
|
title: "Syncpoint Config",
|
|
133
133
|
description: "Configuration for syncpoint backup tool",
|
|
134
134
|
type: "object",
|
|
135
|
-
required: [
|
|
136
|
-
"backup"
|
|
137
|
-
],
|
|
135
|
+
required: ["backup"],
|
|
138
136
|
properties: {
|
|
139
137
|
backup: {
|
|
140
138
|
type: "object",
|
|
141
139
|
description: "Backup configuration",
|
|
142
|
-
required: [
|
|
143
|
-
"targets",
|
|
144
|
-
"exclude",
|
|
145
|
-
"filename"
|
|
146
|
-
],
|
|
140
|
+
required: ["targets", "exclude", "filename"],
|
|
147
141
|
properties: {
|
|
148
142
|
targets: {
|
|
149
143
|
type: "array",
|
package/dist/index.mjs
CHANGED
|
@@ -82,18 +82,12 @@ var config_schema_default = {
|
|
|
82
82
|
title: "Syncpoint Config",
|
|
83
83
|
description: "Configuration for syncpoint backup tool",
|
|
84
84
|
type: "object",
|
|
85
|
-
required: [
|
|
86
|
-
"backup"
|
|
87
|
-
],
|
|
85
|
+
required: ["backup"],
|
|
88
86
|
properties: {
|
|
89
87
|
backup: {
|
|
90
88
|
type: "object",
|
|
91
89
|
description: "Backup configuration",
|
|
92
|
-
required: [
|
|
93
|
-
"targets",
|
|
94
|
-
"exclude",
|
|
95
|
-
"filename"
|
|
96
|
-
],
|
|
90
|
+
required: ["targets", "exclude", "filename"],
|
|
97
91
|
properties: {
|
|
98
92
|
targets: {
|
|
99
93
|
type: "array",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumy-pack/syncpoint",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "CLI tool for project synchronization and scaffolding",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
"main": "dist/index.cjs",
|
|
32
32
|
"module": "dist/index.mjs",
|
|
33
33
|
"types": "dist/index.d.ts",
|
|
34
|
-
"bin": "
|
|
34
|
+
"bin": "dist/cli.mjs",
|
|
35
35
|
"files": [
|
|
36
36
|
"dist",
|
|
37
|
+
"!dist/tsconfig.tsbuildinfo",
|
|
37
38
|
"assets",
|
|
38
39
|
"README.md"
|
|
39
40
|
],
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"version:patch": "yarn version patch"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
59
|
+
"@lumy-pack/shared": "0.0.1",
|
|
58
60
|
"ajv": "^8.0.0",
|
|
59
61
|
"ajv-formats": "^3.0.0",
|
|
60
62
|
"commander": "^12.1.0",
|