@memberjunction/metadata-sync 2.50.0 → 2.52.0

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.
Files changed (39) hide show
  1. package/README.md +423 -2
  2. package/dist/commands/file-reset/index.d.ts +15 -0
  3. package/dist/commands/file-reset/index.js +221 -0
  4. package/dist/commands/file-reset/index.js.map +1 -0
  5. package/dist/commands/pull/index.d.ts +1 -0
  6. package/dist/commands/pull/index.js +82 -10
  7. package/dist/commands/pull/index.js.map +1 -1
  8. package/dist/commands/push/index.d.ts +21 -0
  9. package/dist/commands/push/index.js +589 -45
  10. package/dist/commands/push/index.js.map +1 -1
  11. package/dist/commands/validate/index.d.ts +15 -0
  12. package/dist/commands/validate/index.js +149 -0
  13. package/dist/commands/validate/index.js.map +1 -0
  14. package/dist/commands/watch/index.js +39 -1
  15. package/dist/commands/watch/index.js.map +1 -1
  16. package/dist/config.d.ts +7 -0
  17. package/dist/config.js.map +1 -1
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +5 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/lib/file-backup-manager.d.ts +90 -0
  22. package/dist/lib/file-backup-manager.js +186 -0
  23. package/dist/lib/file-backup-manager.js.map +1 -0
  24. package/dist/lib/provider-utils.d.ts +2 -2
  25. package/dist/lib/provider-utils.js +3 -4
  26. package/dist/lib/provider-utils.js.map +1 -1
  27. package/dist/lib/sync-engine.js +29 -3
  28. package/dist/lib/sync-engine.js.map +1 -1
  29. package/dist/services/FormattingService.d.ts +45 -0
  30. package/dist/services/FormattingService.js +564 -0
  31. package/dist/services/FormattingService.js.map +1 -0
  32. package/dist/services/ValidationService.d.ts +110 -0
  33. package/dist/services/ValidationService.js +737 -0
  34. package/dist/services/ValidationService.js.map +1 -0
  35. package/dist/types/validation.d.ts +98 -0
  36. package/dist/types/validation.js +97 -0
  37. package/dist/types/validation.js.map +1 -0
  38. package/oclif.manifest.json +205 -39
  39. package/package.json +7 -7
package/README.md CHANGED
@@ -806,6 +806,18 @@ Templates can reference other templates:
806
806
  ## CLI Commands
807
807
 
808
808
  ```bash
809
+ # Validate all metadata files
810
+ mj-sync validate
811
+
812
+ # Validate a specific directory
813
+ mj-sync validate --dir="./metadata"
814
+
815
+ # Validate with detailed output
816
+ mj-sync validate --verbose
817
+
818
+ # Validate with JSON output for CI/CD
819
+ mj-sync validate --format=json
820
+
809
821
  # Initialize a directory for metadata sync
810
822
  mj-sync init
811
823
 
@@ -838,8 +850,12 @@ mj-sync status
838
850
  # Watch for changes and auto-push
839
851
  mj-sync watch
840
852
 
841
- # CI/CD mode (push with no prompts)
853
+ # CI/CD mode (push with no prompts, fails on validation errors)
842
854
  mj-sync push --ci
855
+
856
+ # Push/Pull without validation
857
+ mj-sync push --no-validate
858
+ mj-sync pull --entity="AI Prompts" --no-validate
843
859
  ```
844
860
 
845
861
  ## Configuration
@@ -1450,12 +1466,417 @@ Processing AI Prompts in demo/ai-prompts
1450
1466
  - No hardcoded assumptions about entity structure
1451
1467
  - Proper database connection cleanup
1452
1468
 
1469
+ ## Validation System
1470
+
1471
+ The MetadataSync tool includes a comprehensive validation system that checks your metadata files for correctness before pushing to the database. This helps catch errors early and ensures data integrity.
1472
+
1473
+ ### Validation Features
1474
+
1475
+ #### Automatic Validation
1476
+ By default, validation runs automatically before push and pull operations:
1477
+ ```bash
1478
+ # These commands validate first, then proceed if valid
1479
+ mj-sync push
1480
+ mj-sync pull --entity="AI Prompts"
1481
+ ```
1482
+
1483
+ #### Manual Validation
1484
+ Run validation without performing any sync operations:
1485
+ ```bash
1486
+ # Validate current directory
1487
+ mj-sync validate
1488
+
1489
+ # Validate specific directory
1490
+ mj-sync validate --dir="./metadata"
1491
+
1492
+ # Verbose output shows all files checked
1493
+ mj-sync validate --verbose
1494
+ ```
1495
+
1496
+ #### CI/CD Integration
1497
+ Get JSON output for automated pipelines:
1498
+ ```bash
1499
+ # JSON output for parsing
1500
+ mj-sync validate --format=json
1501
+
1502
+ # In CI mode, validation failures cause immediate exit
1503
+ mj-sync push --ci
1504
+ ```
1505
+
1506
+ #### Validation During Push
1507
+
1508
+ **Important:** The `push` command automatically validates your metadata before pushing to the database:
1509
+ - ❌ **Push stops on any validation errors** - You cannot push invalid metadata
1510
+ - 🛑 **In CI mode** - Push fails immediately without prompts
1511
+ - 💬 **In interactive mode** - You'll be asked if you want to continue despite errors
1512
+ - ✅ **Clean validation** - Push proceeds automatically
1513
+
1514
+ #### Skip Validation
1515
+ For emergency fixes or when you know validation will fail:
1516
+ ```bash
1517
+ # Skip validation checks (USE WITH CAUTION!)
1518
+ mj-sync push --no-validate
1519
+ mj-sync pull --entity="AI Prompts" --no-validate
1520
+ ```
1521
+
1522
+ ⚠️ **Warning:** Using `--no-validate` may push invalid metadata to your database, potentially breaking your application. Only use this flag when absolutely necessary.
1523
+
1524
+ ### What Gets Validated
1525
+
1526
+ #### Entity Validation
1527
+ - ✓ Entity names exist in database metadata
1528
+ - ✓ Entity is accessible to current user
1529
+ - ✓ Entity allows data modifications
1530
+
1531
+ #### Field Validation
1532
+ - ✓ Field names exist on the entity
1533
+ - ✓ Virtual properties (getter/setter methods) are automatically detected
1534
+ - ✓ Fields are settable (not system fields)
1535
+ - ✓ Field values match expected data types
1536
+ - ✓ Required fields are checked intelligently:
1537
+ - Skips fields with default values
1538
+ - Skips computed/virtual fields (e.g., `Action` derived from `ActionID`)
1539
+ - Skips fields when related virtual property is used (e.g., `TemplateID` when `TemplateText` is provided)
1540
+ - Skips ReadOnly and AutoUpdateOnly fields
1541
+ - ✓ Foreign key relationships are valid
1542
+
1543
+ #### Reference Validation
1544
+ - ✓ `@file:` references point to existing files
1545
+ - ✓ `@lookup:` references find matching records
1546
+ - ✓ `@template:` references load valid JSON
1547
+ - ✓ `@parent:` and `@root:` have proper context
1548
+ - ✓ Circular references are detected
1549
+
1550
+ #### Best Practice Checks
1551
+ - ⚠️ Deep nesting (>10 levels) generates warnings
1552
+ - ⚠️ Missing required fields are flagged
1553
+ - ⚠️ Large file sizes trigger performance warnings
1554
+ - ⚠️ Naming convention violations
1555
+
1556
+ #### Dependency Order Validation
1557
+ - ✓ Entities are processed in dependency order
1558
+ - ✓ Parent entities exist before children
1559
+ - ✓ Circular dependencies are detected
1560
+ - ✓ Suggests corrected directory order
1561
+
1562
+ ### Validation Output
1563
+
1564
+ #### Human-Readable Format (Default)
1565
+ ```
1566
+ ════════════════════════════════════════════════════════════
1567
+ ║ Validation Report ║
1568
+ ════════════════════════════════════════════════════════════
1569
+
1570
+ ┌────────────────────────────────────────────────┐
1571
+ │ Files: 4 │
1572
+ │ Entities: 29 │
1573
+ │ Errors: 2 │
1574
+ │ Warnings: 5 │
1575
+ ├────────────────────────────────────────────────┤
1576
+ │ Errors by Type: │
1577
+ │ field: 1 │
1578
+ │ reference: 1 │
1579
+ ├────────────────────────────────────────────────┤
1580
+ │ Warnings by Type: │
1581
+ │ bestpractice: 3 │
1582
+ │ nesting: 2 │
1583
+ └────────────────────────────────────────────────┘
1584
+
1585
+ Errors
1586
+
1587
+ 1. Field "Status" does not exist on entity "Templates"
1588
+ Entity: Templates
1589
+ Field: Status
1590
+ File: ./metadata/templates/.my-template.json
1591
+ → Suggestion: Check spelling of 'Status'. Run 'mj-sync list-entities' to see available entities.
1592
+
1593
+ 2. File not found: ./shared/footer.html
1594
+ Entity: Templates
1595
+ Field: FooterHTML
1596
+ File: ./metadata/templates/.my-template.json
1597
+ → Suggestion: Ensure file './shared/footer.html' exists and path is relative to the metadata directory.
1598
+ ```
1599
+
1600
+ #### JSON Format (CI/CD)
1601
+ ```json
1602
+ {
1603
+ "isValid": false,
1604
+ "summary": {
1605
+ "totalFiles": 4,
1606
+ "totalEntities": 29,
1607
+ "totalErrors": 2,
1608
+ "totalWarnings": 5,
1609
+ "errorsByType": {
1610
+ "field": 1,
1611
+ "reference": 1
1612
+ },
1613
+ "warningsByType": {
1614
+ "bestpractice": 3,
1615
+ "nesting": 2
1616
+ }
1617
+ },
1618
+ "errors": [
1619
+ {
1620
+ "type": "field",
1621
+ "entity": "Templates",
1622
+ "field": "Status",
1623
+ "file": "./metadata/templates/.my-template.json",
1624
+ "message": "Field \"Status\" does not exist on entity \"Templates\"",
1625
+ "suggestion": "Check spelling of 'Status'. Run 'mj-sync list-entities' to see available entities."
1626
+ }
1627
+ ],
1628
+ "warnings": [...]
1629
+ }
1630
+ ```
1631
+
1632
+ ### Virtual Properties Support
1633
+
1634
+ Some MemberJunction entities include virtual properties - getter/setter methods that aren't database fields but provide convenient access to related data. The validation system automatically detects these properties.
1635
+
1636
+ #### Example: TemplateText Virtual Property
1637
+ The `Templates` entity includes a `TemplateText` virtual property that:
1638
+ - Automatically manages `Template` and `TemplateContent` records
1639
+ - Isn't a database field but appears as a property on the entity class
1640
+ - Can be used in metadata files just like regular fields
1641
+
1642
+ ```json
1643
+ {
1644
+ "fields": {
1645
+ "Name": "My Template",
1646
+ "TemplateText": "@file:template.html" // Virtual property - works!
1647
+ }
1648
+ }
1649
+ ```
1650
+
1651
+ The validator checks both database metadata AND entity class properties, ensuring virtual properties are properly recognized.
1652
+
1653
+ ### Intelligent Required Field Validation
1654
+
1655
+ The validator intelligently handles required fields to avoid false warnings:
1656
+
1657
+ #### Fields with Default Values
1658
+ Required fields that have database defaults are not flagged:
1659
+ ```json
1660
+ {
1661
+ "fields": {
1662
+ "Name": "My Entity"
1663
+ // CreatedAt is required but has default value - no warning
1664
+ }
1665
+ }
1666
+ ```
1667
+
1668
+ #### Computed/Virtual Fields
1669
+ Fields that are computed from other fields are skipped:
1670
+ ```json
1671
+ {
1672
+ "fields": {
1673
+ "ActionID": "123-456-789"
1674
+ // Action field is computed from ActionID - no warning
1675
+ }
1676
+ }
1677
+ ```
1678
+
1679
+ #### Virtual Property Relationships
1680
+ When using virtual properties, related required fields are skipped:
1681
+ ```json
1682
+ {
1683
+ "fields": {
1684
+ "Name": "My Prompt",
1685
+ "TemplateText": "@file:template.md"
1686
+ // TemplateID and Template are not required when TemplateText is used
1687
+ }
1688
+ }
1689
+ ```
1690
+
1691
+ ### Common Validation Errors
1692
+
1693
+ | Error | Cause | Solution |
1694
+ |-------|-------|----------|
1695
+ | `Field "X" does not exist` | Typo or wrong entity | Check entity definition in generated files |
1696
+ | `Entity "X" not found` | Wrong entity name | Use exact entity name from database |
1697
+ | `File not found` | Bad @file: reference | Check file path is relative and exists |
1698
+ | `Lookup not found` | No matching record | Verify lookup value or use ?create |
1699
+ | `Circular dependency` | A→B→A references | Restructure to avoid cycles |
1700
+ | `Required field missing` | Missing required field | Add field with appropriate value |
1701
+
1702
+ ### Validation Configuration
1703
+
1704
+ Control validation behavior in your workflow:
1705
+
1706
+ ```json
1707
+ {
1708
+ "push": {
1709
+ "validateBeforePush": true // Default: true
1710
+ },
1711
+ "pull": {
1712
+ "validateBeforePull": false // Default: false
1713
+ }
1714
+ }
1715
+ ```
1716
+
1717
+ ### Best Practices
1718
+
1719
+ 1. **Run validation during development**: `mj-sync validate` frequently
1720
+ 2. **Fix errors before warnings**: Errors block operations, warnings don't
1721
+ 3. **Use verbose mode** to understand issues: `mj-sync validate -v`
1722
+ 4. **Include in CI/CD**: Parse JSON output for automated checks
1723
+ 5. **Don't skip validation** unless absolutely necessary
1724
+
1725
+ ## Troubleshooting
1726
+
1727
+ ### Validation Errors
1728
+
1729
+ If validation fails:
1730
+
1731
+ 1. **Read the error message carefully** - It includes specific details
1732
+ 2. **Check the suggestion** - Most errors include how to fix them
1733
+ 3. **Use verbose mode** for more context: `mj-sync validate -v`
1734
+ 4. **Verify entity definitions** in generated entity files
1735
+ 5. **Check file paths** are relative to the metadata directory
1736
+
1737
+ ### Performance Issues
1738
+
1739
+ For large metadata sets:
1740
+
1741
+ 1. **Disable best practice checks**: `mj-sync validate --no-best-practices`
1742
+ 2. **Validate specific directories**: `mj-sync validate --dir="./prompts"`
1743
+ 3. **Reduce nesting depth warning**: `mj-sync validate --max-depth=20`
1744
+
1745
+ ## Programmatic Usage
1746
+
1747
+ ### Using ValidationService in Your Code
1748
+
1749
+ The MetadataSync validation can be used programmatically in any Node.js project:
1750
+
1751
+ ```typescript
1752
+ import { ValidationService, FormattingService } from '@memberjunction/metadata-sync';
1753
+ import { ValidationOptions } from '@memberjunction/metadata-sync/dist/types/validation';
1754
+
1755
+ // Initialize validation options
1756
+ const options: ValidationOptions = {
1757
+ verbose: false,
1758
+ outputFormat: 'human',
1759
+ maxNestingDepth: 10,
1760
+ checkBestPractices: true
1761
+ };
1762
+
1763
+ // Create validator instance
1764
+ const validator = new ValidationService(options);
1765
+
1766
+ // Validate a directory
1767
+ const result = await validator.validateDirectory('/path/to/metadata');
1768
+
1769
+ // Check results
1770
+ if (result.isValid) {
1771
+ console.log('Validation passed!');
1772
+ } else {
1773
+ console.log(`Found ${result.errors.length} errors`);
1774
+
1775
+ // Format results for display
1776
+ const formatter = new FormattingService();
1777
+
1778
+ // Get human-readable output
1779
+ const humanOutput = formatter.formatValidationResult(result, true);
1780
+ console.log(humanOutput);
1781
+
1782
+ // Get JSON output
1783
+ const jsonOutput = formatter.formatValidationResultAsJson(result);
1784
+
1785
+ // Get beautiful markdown report
1786
+ const markdownReport = formatter.formatValidationResultAsMarkdown(result);
1787
+ }
1788
+ ```
1789
+
1790
+ ### ValidationResult Structure
1791
+
1792
+ The validation service returns a structured object with complete details:
1793
+
1794
+ ```typescript
1795
+ interface ValidationResult {
1796
+ isValid: boolean;
1797
+ errors: ValidationError[];
1798
+ warnings: ValidationWarning[];
1799
+ summary: {
1800
+ totalFiles: number;
1801
+ totalEntities: number;
1802
+ totalErrors: number;
1803
+ totalWarnings: number;
1804
+ fileResults: Map<string, FileValidationResult>;
1805
+ };
1806
+ }
1807
+
1808
+ interface ValidationError {
1809
+ type: 'entity' | 'field' | 'reference' | 'circular' | 'dependency' | 'nesting' | 'bestpractice';
1810
+ severity: 'error' | 'warning';
1811
+ entity?: string;
1812
+ field?: string;
1813
+ file: string;
1814
+ message: string;
1815
+ suggestion?: string;
1816
+ details?: any;
1817
+ }
1818
+ ```
1819
+
1820
+ ### Integration Example
1821
+
1822
+ ```typescript
1823
+ import { ValidationService } from '@memberjunction/metadata-sync';
1824
+
1825
+ export async function validateBeforeDeploy(metadataPath: string): Promise<boolean> {
1826
+ const validator = new ValidationService({
1827
+ checkBestPractices: true,
1828
+ maxNestingDepth: 10
1829
+ });
1830
+
1831
+ const result = await validator.validateDirectory(metadataPath);
1832
+
1833
+ if (!result.isValid) {
1834
+ // Log errors to your monitoring system
1835
+ result.errors.forEach(error => {
1836
+ logger.error('Metadata validation error', {
1837
+ type: error.type,
1838
+ entity: error.entity,
1839
+ field: error.field,
1840
+ file: error.file,
1841
+ message: error.message
1842
+ });
1843
+ });
1844
+
1845
+ // Optionally save report
1846
+ const report = new FormattingService().formatValidationResultAsMarkdown(result);
1847
+ await fs.writeFile('validation-report.md', report);
1848
+
1849
+ return false;
1850
+ }
1851
+
1852
+ return true;
1853
+ }
1854
+ ```
1855
+
1856
+ ### CI/CD Integration
1857
+
1858
+ ```yaml
1859
+ # Example GitHub Actions workflow
1860
+ - name: Validate Metadata
1861
+ run: |
1862
+ npm install @memberjunction/metadata-sync
1863
+ npx mj-sync validate --dir=./metadata --format=json > validation-results.json
1864
+
1865
+ - name: Check Validation Results
1866
+ run: |
1867
+ if [ $(jq '.isValid' validation-results.json) = "false" ]; then
1868
+ echo "Metadata validation failed!"
1869
+ jq '.errors' validation-results.json
1870
+ exit 1
1871
+ fi
1872
+ ```
1873
+
1453
1874
  ## Future Enhancements
1454
1875
 
1455
1876
  - Plugin system for custom entity handlers
1456
1877
  - Merge conflict resolution UI
1457
1878
  - Bulk operations across entities
1458
- - Metadata validation rules
1879
+ - Extended validation rules
1459
1880
  - Schema migration support
1460
1881
  - Team collaboration features
1461
1882
  - Bidirectional sync for related entities
@@ -0,0 +1,15 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class FileReset extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ sections: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ 'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ 'no-backup': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ private countSections;
14
+ private removeSections;
15
+ }
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@oclif/core");
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const prompts_1 = require("@inquirer/prompts");
10
+ const ora_classic_1 = __importDefault(require("ora-classic"));
11
+ const fast_glob_1 = __importDefault(require("fast-glob"));
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ const config_1 = require("../../config");
14
+ const config_manager_1 = require("../../lib/config-manager");
15
+ class FileReset extends core_1.Command {
16
+ static description = 'Remove primaryKey and sync sections from metadata JSON files';
17
+ static examples = [
18
+ `<%= config.bin %> <%= command.id %>`,
19
+ `<%= config.bin %> <%= command.id %> --sections=primaryKey`,
20
+ `<%= config.bin %> <%= command.id %> --sections=sync`,
21
+ `<%= config.bin %> <%= command.id %> --dry-run`,
22
+ `<%= config.bin %> <%= command.id %> --no-backup`,
23
+ `<%= config.bin %> <%= command.id %> --yes`,
24
+ ];
25
+ static flags = {
26
+ sections: core_1.Flags.string({
27
+ description: 'Which sections to remove',
28
+ options: ['both', 'primaryKey', 'sync'],
29
+ default: 'both',
30
+ }),
31
+ 'dry-run': core_1.Flags.boolean({
32
+ description: 'Show what would be removed without actually removing'
33
+ }),
34
+ 'no-backup': core_1.Flags.boolean({
35
+ description: 'Skip creating backup files'
36
+ }),
37
+ yes: core_1.Flags.boolean({
38
+ char: 'y',
39
+ description: 'Skip confirmation prompt'
40
+ }),
41
+ verbose: core_1.Flags.boolean({
42
+ char: 'v',
43
+ description: 'Show detailed output'
44
+ }),
45
+ };
46
+ async run() {
47
+ const { flags } = await this.parse(FileReset);
48
+ const spinner = (0, ora_classic_1.default)();
49
+ try {
50
+ // Load sync config
51
+ const syncConfig = await (0, config_1.loadSyncConfig)(config_manager_1.configManager.getOriginalCwd());
52
+ if (!syncConfig) {
53
+ this.error('No .mj-sync.json found in current directory');
54
+ }
55
+ // Find all metadata JSON files
56
+ spinner.start('Finding metadata files');
57
+ const pattern = syncConfig.filePattern || '.*.json';
58
+ const files = await (0, fast_glob_1.default)(pattern, {
59
+ cwd: config_manager_1.configManager.getOriginalCwd(),
60
+ absolute: true,
61
+ ignore: ['.mj-sync.json', '.mj-folder.json'],
62
+ });
63
+ spinner.stop();
64
+ if (files.length === 0) {
65
+ this.log('No metadata files found');
66
+ return;
67
+ }
68
+ this.log(`Found ${files.length} metadata file${files.length === 1 ? '' : 's'}`);
69
+ // Count what will be removed
70
+ let filesWithPrimaryKey = 0;
71
+ let filesWithSync = 0;
72
+ let totalPrimaryKeys = 0;
73
+ let totalSyncs = 0;
74
+ for (const file of files) {
75
+ const content = await fs_extra_1.default.readJson(file);
76
+ const stats = this.countSections(content);
77
+ if (stats.primaryKeyCount > 0) {
78
+ filesWithPrimaryKey++;
79
+ totalPrimaryKeys += stats.primaryKeyCount;
80
+ }
81
+ if (stats.syncCount > 0) {
82
+ filesWithSync++;
83
+ totalSyncs += stats.syncCount;
84
+ }
85
+ }
86
+ // Show what will be removed
87
+ this.log('');
88
+ if (flags.sections === 'both' || flags.sections === 'primaryKey') {
89
+ this.log(`Will remove ${chalk_1.default.yellow(totalPrimaryKeys)} primaryKey section${totalPrimaryKeys === 1 ? '' : 's'} from ${chalk_1.default.yellow(filesWithPrimaryKey)} file${filesWithPrimaryKey === 1 ? '' : 's'}`);
90
+ }
91
+ if (flags.sections === 'both' || flags.sections === 'sync') {
92
+ this.log(`Will remove ${chalk_1.default.yellow(totalSyncs)} sync section${totalSyncs === 1 ? '' : 's'} from ${chalk_1.default.yellow(filesWithSync)} file${filesWithSync === 1 ? '' : 's'}`);
93
+ }
94
+ if (flags['dry-run']) {
95
+ this.log('');
96
+ this.log(chalk_1.default.cyan('Dry run mode - no files will be modified'));
97
+ if (flags.verbose) {
98
+ this.log('');
99
+ for (const file of files) {
100
+ const content = await fs_extra_1.default.readJson(file);
101
+ const stats = this.countSections(content);
102
+ if (stats.primaryKeyCount > 0 || stats.syncCount > 0) {
103
+ this.log(`${path_1.default.relative(config_manager_1.configManager.getOriginalCwd(), file)}:`);
104
+ if (stats.primaryKeyCount > 0) {
105
+ this.log(` - ${stats.primaryKeyCount} primaryKey section${stats.primaryKeyCount === 1 ? '' : 's'}`);
106
+ }
107
+ if (stats.syncCount > 0) {
108
+ this.log(` - ${stats.syncCount} sync section${stats.syncCount === 1 ? '' : 's'}`);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ return;
114
+ }
115
+ // Confirm before proceeding
116
+ if (!flags.yes) {
117
+ const confirmed = await (0, prompts_1.confirm)({
118
+ message: 'Do you want to proceed?',
119
+ default: false,
120
+ });
121
+ if (!confirmed) {
122
+ this.log('Operation cancelled');
123
+ return;
124
+ }
125
+ }
126
+ // Process files
127
+ spinner.start('Processing files');
128
+ let processedFiles = 0;
129
+ let modifiedFiles = 0;
130
+ for (const file of files) {
131
+ processedFiles++;
132
+ const content = await fs_extra_1.default.readJson(file);
133
+ const originalContent = JSON.stringify(content);
134
+ // Remove sections
135
+ const cleanedContent = this.removeSections(content, flags.sections);
136
+ // Only write if content changed
137
+ if (JSON.stringify(cleanedContent) !== originalContent) {
138
+ // Create backup if requested
139
+ if (!flags['no-backup']) {
140
+ const backupPath = `${file}.backup`;
141
+ await fs_extra_1.default.writeJson(backupPath, content, { spaces: 2 });
142
+ }
143
+ // Write cleaned content
144
+ await fs_extra_1.default.writeJson(file, cleanedContent, { spaces: 2 });
145
+ modifiedFiles++;
146
+ if (flags.verbose) {
147
+ spinner.stop();
148
+ this.log(`✓ ${path_1.default.relative(config_manager_1.configManager.getOriginalCwd(), file)}`);
149
+ spinner.start('Processing files');
150
+ }
151
+ }
152
+ }
153
+ spinner.stop();
154
+ // Show summary
155
+ this.log('');
156
+ this.log(chalk_1.default.green(`✓ Reset complete`));
157
+ this.log(` Processed: ${processedFiles} file${processedFiles === 1 ? '' : 's'}`);
158
+ this.log(` Modified: ${modifiedFiles} file${modifiedFiles === 1 ? '' : 's'}`);
159
+ if (!flags['no-backup'] && modifiedFiles > 0) {
160
+ this.log(` Backups created: ${modifiedFiles}`);
161
+ }
162
+ }
163
+ catch (error) {
164
+ spinner.stop();
165
+ this.error(error instanceof Error ? error.message : String(error));
166
+ }
167
+ }
168
+ countSections(data) {
169
+ let primaryKeyCount = 0;
170
+ let syncCount = 0;
171
+ if (Array.isArray(data)) {
172
+ for (const item of data) {
173
+ const stats = this.countSections(item);
174
+ primaryKeyCount += stats.primaryKeyCount;
175
+ syncCount += stats.syncCount;
176
+ }
177
+ }
178
+ else if (data && typeof data === 'object') {
179
+ if ('primaryKey' in data)
180
+ primaryKeyCount++;
181
+ if ('sync' in data)
182
+ syncCount++;
183
+ // Check related entities
184
+ if (data.relatedEntities) {
185
+ for (const entityData of Object.values(data.relatedEntities)) {
186
+ const stats = this.countSections(entityData);
187
+ primaryKeyCount += stats.primaryKeyCount;
188
+ syncCount += stats.syncCount;
189
+ }
190
+ }
191
+ }
192
+ return { primaryKeyCount, syncCount };
193
+ }
194
+ removeSections(data, sections) {
195
+ if (Array.isArray(data)) {
196
+ return data.map(item => this.removeSections(item, sections));
197
+ }
198
+ else if (data && typeof data === 'object') {
199
+ const cleaned = { ...data };
200
+ // Remove specified sections
201
+ if (sections === 'both' || sections === 'primaryKey') {
202
+ delete cleaned.primaryKey;
203
+ }
204
+ if (sections === 'both' || sections === 'sync') {
205
+ delete cleaned.sync;
206
+ }
207
+ // Process related entities
208
+ if (cleaned.relatedEntities) {
209
+ const cleanedRelated = {};
210
+ for (const [entityName, entityData] of Object.entries(cleaned.relatedEntities)) {
211
+ cleanedRelated[entityName] = this.removeSections(entityData, sections);
212
+ }
213
+ cleaned.relatedEntities = cleanedRelated;
214
+ }
215
+ return cleaned;
216
+ }
217
+ return data;
218
+ }
219
+ }
220
+ exports.default = FileReset;
221
+ //# sourceMappingURL=index.js.map