@memberjunction/metadata-sync 2.46.0 → 2.48.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.
- package/README.md +341 -28
- package/dist/commands/pull/index.d.ts +220 -0
- package/dist/commands/pull/index.js +1094 -113
- package/dist/commands/pull/index.js.map +1 -1
- package/dist/commands/push/index.d.ts +1 -0
- package/dist/commands/push/index.js +90 -40
- package/dist/commands/push/index.js.map +1 -1
- package/dist/commands/status/index.js +51 -7
- package/dist/commands/status/index.js.map +1 -1
- package/dist/commands/watch/index.js +20 -7
- package/dist/commands/watch/index.js.map +1 -1
- package/dist/config.d.ts +210 -0
- package/dist/config.js +83 -13
- package/dist/config.js.map +1 -1
- package/dist/hooks/init.js +9 -1
- package/dist/hooks/init.js.map +1 -1
- package/dist/lib/config-manager.d.ts +56 -0
- package/dist/lib/config-manager.js +104 -0
- package/dist/lib/config-manager.js.map +1 -0
- package/dist/lib/provider-utils.d.ts +76 -4
- package/dist/lib/provider-utils.js +136 -52
- package/dist/lib/provider-utils.js.map +1 -1
- package/dist/lib/singleton-manager.d.ts +34 -0
- package/dist/lib/singleton-manager.js +62 -0
- package/dist/lib/singleton-manager.js.map +1 -0
- package/dist/lib/sync-engine.d.ts +239 -5
- package/dist/lib/sync-engine.js +314 -5
- package/dist/lib/sync-engine.js.map +1 -1
- package/oclif.manifest.json +51 -37
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -74,18 +74,29 @@ The Metadata Sync tool bridges the gap between database-stored metadata and file
|
|
|
74
74
|
|
|
75
75
|
## Supported Entities
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
The tool works with any MemberJunction entity - both core system entities and user-created entities. Each entity type can have its own directory structure, file naming conventions, and related entity configurations.
|
|
78
|
+
|
|
79
|
+
### Important Limitation: Database-Reflected Metadata
|
|
80
|
+
|
|
81
|
+
**This tool should NOT be used to modify metadata that is reflected from the underlying database catalog.** Examples include:
|
|
82
|
+
- Entity field data types
|
|
83
|
+
- Column lengths/precision
|
|
84
|
+
- Primary key definitions
|
|
85
|
+
- Foreign key relationships
|
|
86
|
+
- Table/column existence
|
|
87
|
+
|
|
88
|
+
These properties are designed to flow **from** the database catalog **up** into MJ metadata, not the other way around. Attempting to modify these via file sync could create inconsistencies between the metadata and actual database schema.
|
|
89
|
+
|
|
90
|
+
The tool is intended for managing business-level metadata such as:
|
|
91
|
+
- Descriptions and documentation
|
|
92
|
+
- Display names and user-facing text
|
|
93
|
+
- Categories and groupings
|
|
94
|
+
- Custom properties and settings
|
|
95
|
+
- AI prompts, templates, and other content
|
|
96
|
+
- Permissions and security settings
|
|
97
|
+
- Any other data that is not reflected **up** from the underlying system database catalogs
|
|
98
|
+
|
|
99
|
+
For more information about how CodeGen reflects system-level data from the database into the MJ metadata layer, see the [CodeGen documentation](../CodeGen/README.md).
|
|
89
100
|
|
|
90
101
|
## File Structure
|
|
91
102
|
|
|
@@ -93,10 +104,55 @@ The tool uses a hierarchical directory structure with cascading defaults:
|
|
|
93
104
|
- Each top-level directory represents an entity type
|
|
94
105
|
- `.mj-sync.json` files define entities and base defaults
|
|
95
106
|
- `.mj-folder.json` files define folder-specific defaults (optional)
|
|
96
|
-
-
|
|
107
|
+
- Only dot-prefixed JSON files (e.g., `.prompt-template.json`, `.category.json`) are treated as metadata records
|
|
108
|
+
- Regular JSON files without the dot prefix are ignored, allowing package.json and other config files to coexist
|
|
97
109
|
- External files (`.md`, `.html`, etc.) are referenced from the JSON files
|
|
98
110
|
- Defaults cascade down through the folder hierarchy
|
|
99
111
|
|
|
112
|
+
### File Naming Convention
|
|
113
|
+
|
|
114
|
+
**Metadata files must be prefixed with a dot (.)** to be recognized by the sync tool. This convention:
|
|
115
|
+
- Clearly distinguishes metadata files from regular configuration files
|
|
116
|
+
- Allows `package.json`, `tsconfig.json` and other standard files to coexist without being processed
|
|
117
|
+
- Follows established patterns like `.gitignore` and `.eslintrc.json`
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
- ✅ `.greeting.json` - Will be processed as metadata
|
|
121
|
+
- ✅ `.customer-prompt.json` - Will be processed as metadata
|
|
122
|
+
- ❌ `greeting.json` - Will be ignored
|
|
123
|
+
- ❌ `package.json` - Will be ignored
|
|
124
|
+
|
|
125
|
+
### File Format Options
|
|
126
|
+
|
|
127
|
+
#### Single Record per File (Default)
|
|
128
|
+
Each JSON file contains one record:
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"fields": { ... },
|
|
132
|
+
"relatedEntities": { ... }
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Multiple Records per File (NEW)
|
|
137
|
+
JSON files can contain arrays of records:
|
|
138
|
+
```json
|
|
139
|
+
[
|
|
140
|
+
{
|
|
141
|
+
"fields": { ... },
|
|
142
|
+
"relatedEntities": { ... }
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"fields": { ... },
|
|
146
|
+
"relatedEntities": { ... }
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This is useful for:
|
|
152
|
+
- Grouping related records in a single file
|
|
153
|
+
- Reducing file clutter for entities with many small records
|
|
154
|
+
- Maintaining logical groupings while using `@file:` references for large content
|
|
155
|
+
|
|
100
156
|
### Example Structure
|
|
101
157
|
```
|
|
102
158
|
metadata/
|
|
@@ -105,28 +161,33 @@ metadata/
|
|
|
105
161
|
│ ├── .mj-sync.json # Defines entity: "AI Prompts"
|
|
106
162
|
│ ├── customer-service/
|
|
107
163
|
│ │ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
|
|
108
|
-
│ │ ├── greeting.json
|
|
164
|
+
│ │ ├── .greeting.json # AI Prompt record with embedded models
|
|
109
165
|
│ │ ├── greeting.prompt.md # Prompt content (referenced)
|
|
110
166
|
│ │ └── greeting.notes.md # Notes field (referenced)
|
|
111
167
|
│ └── analytics/
|
|
112
168
|
│ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
|
|
113
|
-
│ ├── daily-report.json
|
|
169
|
+
│ ├── .daily-report.json # AI Prompt record
|
|
114
170
|
│ └── daily-report.prompt.md # Prompt content (referenced)
|
|
115
|
-
|
|
171
|
+
├── templates/ # Reusable JSON templates
|
|
172
|
+
│ ├── standard-prompt-settings.json # Common prompt configurations
|
|
173
|
+
│ ├── standard-ai-models.json # Standard model configurations
|
|
174
|
+
│ ├── high-performance-models.json # High-power model configurations
|
|
175
|
+
│ └── customer-service-defaults.json # CS-specific defaults
|
|
176
|
+
└── template-entities/
|
|
116
177
|
├── .mj-sync.json # Defines entity: "Templates"
|
|
117
178
|
├── email/
|
|
118
179
|
│ ├── .mj-folder.json # Folder metadata
|
|
119
|
-
│ ├── welcome.json
|
|
180
|
+
│ ├── .welcome.json # Template record (dot-prefixed)
|
|
120
181
|
│ └── welcome.template.html # Template content (referenced)
|
|
121
182
|
└── reports/
|
|
122
183
|
├── .mj-folder.json # Folder metadata
|
|
123
|
-
├── invoice.json
|
|
184
|
+
├── .invoice.json # Template record (dot-prefixed)
|
|
124
185
|
└── invoice.template.html # Template content (referenced)
|
|
125
186
|
```
|
|
126
187
|
|
|
127
188
|
## JSON Metadata Format
|
|
128
189
|
|
|
129
|
-
### Individual Record (e.g., ai-prompts/customer-service
|
|
190
|
+
### Individual Record (e.g., ai-prompts/customer-service/.greeting.json)
|
|
130
191
|
```json
|
|
131
192
|
{
|
|
132
193
|
"fields": {
|
|
@@ -302,6 +363,66 @@ Support environment-specific values:
|
|
|
302
363
|
- `@env:VARIABLE_NAME`
|
|
303
364
|
- Useful for different environments (dev/staging/prod)
|
|
304
365
|
|
|
366
|
+
### @template: References (NEW)
|
|
367
|
+
Enable JSON template composition for reusable configurations:
|
|
368
|
+
|
|
369
|
+
#### String Template Reference
|
|
370
|
+
Use `@template:` to replace any value with template content:
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"relatedEntities": {
|
|
374
|
+
"MJ: AI Prompt Models": "@template:templates/standard-ai-models.json"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### Object Template Merging
|
|
380
|
+
Use `@template` field within objects to merge template content:
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"fields": {
|
|
384
|
+
"Name": "My Prompt",
|
|
385
|
+
"@template": "templates/standard-prompt-settings.json",
|
|
386
|
+
"Temperature": 0.9 // Overrides template value
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### Multiple Template Merging
|
|
392
|
+
Merge multiple templates in order (later templates override earlier ones):
|
|
393
|
+
```json
|
|
394
|
+
{
|
|
395
|
+
"fields": {
|
|
396
|
+
"@template": [
|
|
397
|
+
"templates/base-settings.json",
|
|
398
|
+
"templates/customer-service-defaults.json"
|
|
399
|
+
],
|
|
400
|
+
"Name": "Customer Bot" // Local fields override all templates
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### Nested Templates
|
|
406
|
+
Templates can reference other templates:
|
|
407
|
+
```json
|
|
408
|
+
// templates/high-performance-models.json
|
|
409
|
+
[
|
|
410
|
+
{
|
|
411
|
+
"fields": {
|
|
412
|
+
"@template": "../templates/model-defaults.json",
|
|
413
|
+
"ModelID": "@lookup:AI Models.Name=GPT 4o"
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### Template Benefits
|
|
420
|
+
- **DRY Principle**: Define configurations once, use everywhere
|
|
421
|
+
- **Maintainability**: Update template to affect all uses
|
|
422
|
+
- **Flexibility**: Use at any JSON level
|
|
423
|
+
- **Composability**: Build complex configurations from simple parts
|
|
424
|
+
- **Override Support**: Local values always override template values
|
|
425
|
+
|
|
305
426
|
## CLI Commands
|
|
306
427
|
|
|
307
428
|
```bash
|
|
@@ -314,6 +435,10 @@ mj-sync pull --entity="AI Prompts"
|
|
|
314
435
|
# Pull specific records by filter
|
|
315
436
|
mj-sync pull --entity="AI Prompts" --filter="CategoryID='customer-service-id'"
|
|
316
437
|
|
|
438
|
+
# Pull multiple records into a single file (NEW)
|
|
439
|
+
mj-sync pull --entity="AI Prompts" --multi-file="all-prompts"
|
|
440
|
+
mj-sync pull --entity="AI Prompts" --filter="Status='Active'" --multi-file="active-prompts.json"
|
|
441
|
+
|
|
317
442
|
# Push all changes from current directory and subdirectories
|
|
318
443
|
mj-sync push
|
|
319
444
|
|
|
@@ -365,7 +490,7 @@ Configuration follows a hierarchical structure:
|
|
|
365
490
|
```json
|
|
366
491
|
{
|
|
367
492
|
"entity": "AI Prompts",
|
|
368
|
-
"filePattern": "
|
|
493
|
+
"filePattern": ".*.json",
|
|
369
494
|
"defaults": {
|
|
370
495
|
"TypeID": "@lookup:AI Prompt Types.Name=Chat",
|
|
371
496
|
"Temperature": 0.7,
|
|
@@ -373,11 +498,21 @@ Configuration follows a hierarchical structure:
|
|
|
373
498
|
"Status": "Active"
|
|
374
499
|
},
|
|
375
500
|
"pull": {
|
|
501
|
+
"filePattern": ".*.json",
|
|
502
|
+
"updateExistingRecords": true,
|
|
503
|
+
"createNewFileIfNotFound": true,
|
|
504
|
+
"mergeStrategy": "merge",
|
|
376
505
|
"filter": "Status = 'Active'",
|
|
506
|
+
"externalizeFields": [
|
|
507
|
+
{
|
|
508
|
+
"field": "Prompt",
|
|
509
|
+
"pattern": "@file:{Name}.prompt.md"
|
|
510
|
+
}
|
|
511
|
+
],
|
|
377
512
|
"relatedEntities": {
|
|
378
513
|
"MJ: AI Prompt Models": {
|
|
379
514
|
"entity": "MJ: AI Prompt Models",
|
|
380
|
-
"foreignKey": "
|
|
515
|
+
"foreignKey": "PromptID",
|
|
381
516
|
"filter": "Status = 'Active'"
|
|
382
517
|
}
|
|
383
518
|
}
|
|
@@ -418,26 +553,204 @@ The tool now supports managing related entities as embedded collections within p
|
|
|
418
553
|
- **Relationship Clarity**: Visual representation of data relationships
|
|
419
554
|
|
|
420
555
|
### Configuration for Pull
|
|
421
|
-
|
|
556
|
+
|
|
557
|
+
The pull command now supports smart update capabilities with extensive configuration options:
|
|
558
|
+
|
|
422
559
|
```json
|
|
423
560
|
{
|
|
424
561
|
"entity": "AI Prompts",
|
|
562
|
+
"filePattern": ".*.json",
|
|
425
563
|
"pull": {
|
|
564
|
+
"filePattern": ".*.json",
|
|
565
|
+
"createNewFileIfNotFound": true,
|
|
566
|
+
"newFileName": ".all-new.json",
|
|
567
|
+
"appendRecordsToExistingFile": true,
|
|
568
|
+
"updateExistingRecords": true,
|
|
569
|
+
"preserveFields": ["customField", "localNotes"],
|
|
570
|
+
"mergeStrategy": "merge",
|
|
571
|
+
"backupBeforeUpdate": true,
|
|
572
|
+
"filter": "Status = 'Active'",
|
|
573
|
+
"externalizeFields": [
|
|
574
|
+
{
|
|
575
|
+
"field": "TemplateText",
|
|
576
|
+
"pattern": "@file:{Name}.template.md"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"field": "PromptText",
|
|
580
|
+
"pattern": "@file:prompts/{Name}.prompt.md"
|
|
581
|
+
}
|
|
582
|
+
],
|
|
583
|
+
"excludeFields": ["InternalID", "TempField"],
|
|
584
|
+
"lookupFields": {
|
|
585
|
+
"CategoryID": {
|
|
586
|
+
"entity": "AI Prompt Categories",
|
|
587
|
+
"field": "Name"
|
|
588
|
+
},
|
|
589
|
+
"TypeID": {
|
|
590
|
+
"entity": "AI Prompt Types",
|
|
591
|
+
"field": "Name"
|
|
592
|
+
}
|
|
593
|
+
},
|
|
426
594
|
"relatedEntities": {
|
|
427
595
|
"MJ: AI Prompt Models": {
|
|
428
596
|
"entity": "MJ: AI Prompt Models",
|
|
429
|
-
"foreignKey": "
|
|
430
|
-
"filter": "Status = 'Active'"
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
597
|
+
"foreignKey": "PromptID",
|
|
598
|
+
"filter": "Status = 'Active'",
|
|
599
|
+
"lookupFields": {
|
|
600
|
+
"ModelID": {
|
|
601
|
+
"entity": "AI Models",
|
|
602
|
+
"field": "Name"
|
|
603
|
+
}
|
|
604
|
+
}
|
|
435
605
|
}
|
|
436
606
|
}
|
|
437
607
|
}
|
|
438
608
|
}
|
|
439
609
|
```
|
|
440
610
|
|
|
611
|
+
#### Pull Configuration Options
|
|
612
|
+
|
|
613
|
+
| Option | Type | Default | Description |
|
|
614
|
+
|--------|------|---------|-------------|
|
|
615
|
+
| `filePattern` | string | Entity filePattern | Pattern for finding existing files to update |
|
|
616
|
+
| `createNewFileIfNotFound` | boolean | true | Create files for records not found locally |
|
|
617
|
+
| `newFileName` | string | - | Filename for new records when appending (see warning below) |
|
|
618
|
+
| `appendRecordsToExistingFile` | boolean | false | Append new records to a single file |
|
|
619
|
+
| `updateExistingRecords` | boolean | true | Update existing records found in local files |
|
|
620
|
+
| `preserveFields` | string[] | [] | Fields that retain local values during updates (see detailed explanation below) |
|
|
621
|
+
| `mergeStrategy` | string | "merge" | How to merge updates: "merge", "overwrite", or "skip" |
|
|
622
|
+
| `backupBeforeUpdate` | boolean | false | Create timestamped backups before updating files |
|
|
623
|
+
| `backupDirectory` | string | ".backups" | Directory name for backup files (relative to entity directory) |
|
|
624
|
+
| `filter` | string | - | SQL WHERE clause for filtering records |
|
|
625
|
+
| `externalizeFields` | array/object | - | Fields to save as external files with optional patterns |
|
|
626
|
+
| `excludeFields` | string[] | [] | Fields to completely omit from pulled data (see detailed explanation below) |
|
|
627
|
+
| `lookupFields` | object | - | Foreign keys to convert to @lookup references |
|
|
628
|
+
| `relatedEntities` | object | - | Related entities to pull as embedded collections |
|
|
629
|
+
|
|
630
|
+
> **⚠️ Important Configuration Warning**
|
|
631
|
+
>
|
|
632
|
+
> When both `appendRecordsToExistingFile: true` and `newFileName` are set, ALL new records will be appended to the single file specified by `newFileName`, effectively ignoring the standard per-record file pattern. This can lead to unexpected file organization:
|
|
633
|
+
>
|
|
634
|
+
> ```json
|
|
635
|
+
> // This configuration will put ALL new records in .all-new.json
|
|
636
|
+
> "pull": {
|
|
637
|
+
> "appendRecordsToExistingFile": true,
|
|
638
|
+
> "newFileName": ".all-new.json" // ⚠️ Overrides individual file creation
|
|
639
|
+
> }
|
|
640
|
+
> ```
|
|
641
|
+
>
|
|
642
|
+
> **Recommended configurations:**
|
|
643
|
+
> - For individual files per record: Set `appendRecordsToExistingFile: false` (or omit it)
|
|
644
|
+
> - For grouped new records: Set both `appendRecordsToExistingFile: true` and `newFileName`
|
|
645
|
+
> - For mixed approach: Omit `newFileName` to let new records follow the standard pattern
|
|
646
|
+
|
|
647
|
+
#### Merge Strategies
|
|
648
|
+
|
|
649
|
+
- **`merge`** (default): Combines fields from database and local file, with database values taking precedence for existing fields
|
|
650
|
+
- **`overwrite`**: Completely replaces local record with database version (except preserved fields)
|
|
651
|
+
- **`skip`**: Leaves existing records unchanged, only adds new records
|
|
652
|
+
|
|
653
|
+
#### Understanding excludeFields vs preserveFields
|
|
654
|
+
|
|
655
|
+
These two configuration options serve different purposes for managing fields during pull operations:
|
|
656
|
+
|
|
657
|
+
##### excludeFields
|
|
658
|
+
- **Purpose**: Completely omit specified fields from your local files
|
|
659
|
+
- **Use Case**: Remove internal/system fields you don't want in version control
|
|
660
|
+
- **Effect**: Fields never appear in the JSON files
|
|
661
|
+
- **Example**: Excluding internal IDs, timestamps, or sensitive data
|
|
662
|
+
|
|
663
|
+
##### preserveFields
|
|
664
|
+
- **Purpose**: Protect local customizations from being overwritten during updates
|
|
665
|
+
- **Use Case**: Keep locally modified values while updating other fields
|
|
666
|
+
- **Effect**: Fields exist in files but retain their local values during pull
|
|
667
|
+
- **Example**: Preserving custom file paths, local notes, or environment-specific values
|
|
668
|
+
- **Special Behavior for @file: references**: When a preserved field contains a `@file:` reference, the tool will update the content at the existing file path rather than creating a new file with a generated name
|
|
669
|
+
|
|
670
|
+
##### Example Configuration
|
|
671
|
+
```json
|
|
672
|
+
{
|
|
673
|
+
"pull": {
|
|
674
|
+
"excludeFields": ["TemplateID", "InternalNotes", "CreatedAt"],
|
|
675
|
+
"preserveFields": ["TemplateText", "OutputExample", "LocalConfig"]
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
With this configuration:
|
|
681
|
+
- **TemplateID, InternalNotes, CreatedAt** → Never appear in local files
|
|
682
|
+
- **TemplateText, OutputExample, LocalConfig** → Keep their local values during updates
|
|
683
|
+
|
|
684
|
+
##### Common Scenario: Customized File References
|
|
685
|
+
When you customize file paths (e.g., changing `@file:templates/skip-conductor.md` to `@file:templates/conductor.md`), use `preserveFields` to protect these customizations:
|
|
686
|
+
|
|
687
|
+
```json
|
|
688
|
+
{
|
|
689
|
+
"pull": {
|
|
690
|
+
"preserveFields": ["TemplateText", "OutputExample"],
|
|
691
|
+
"externalizeFields": [
|
|
692
|
+
{
|
|
693
|
+
"field": "TemplateText",
|
|
694
|
+
"pattern": "@file:templates/{Name}.template.md"
|
|
695
|
+
}
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
This ensures your custom paths aren't overwritten when pulling updates from the database.
|
|
702
|
+
|
|
703
|
+
**How it works:**
|
|
704
|
+
1. **Without preserveFields**: Pull would create a new file using the pattern (e.g., `templates/skip-conductor.template.md`) and update the JSON to point to it
|
|
705
|
+
2. **With preserveFields**: Pull keeps your custom path (e.g., `@file:templates/conductor.md`) in the JSON and updates the content at that existing location
|
|
706
|
+
|
|
707
|
+
This is particularly useful when:
|
|
708
|
+
- You've reorganized your file structure after initial pull
|
|
709
|
+
- You've renamed files to follow your own naming conventions
|
|
710
|
+
- You want to maintain consistent paths across team members
|
|
711
|
+
|
|
712
|
+
#### Backup Configuration
|
|
713
|
+
|
|
714
|
+
When `backupBeforeUpdate` is enabled, the tool creates timestamped backups before updating existing files:
|
|
715
|
+
|
|
716
|
+
- **Backup Location**: Files are backed up to the `backupDirectory` (default: `.backups`) within the entity directory
|
|
717
|
+
- **Backup Naming**: Original filename + timestamp + `.backup` extension (e.g., `.greeting.json` → `.greeting.2024-03-15T10-30-45-123Z.backup`)
|
|
718
|
+
- **Extension**: All backup files use the `.backup` extension, preventing them from being processed by push/pull/status commands
|
|
719
|
+
- **Deduplication**: Only one backup is created per file per pull operation, even if the file contains multiple records
|
|
720
|
+
|
|
721
|
+
Example configuration:
|
|
722
|
+
```json
|
|
723
|
+
"pull": {
|
|
724
|
+
"backupBeforeUpdate": true,
|
|
725
|
+
"backupDirectory": ".backups" // Custom backup directory name
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
#### Externalize Fields Patterns
|
|
730
|
+
|
|
731
|
+
The `externalizeFields` configuration supports dynamic file naming with placeholders:
|
|
732
|
+
|
|
733
|
+
```json
|
|
734
|
+
"externalizeFields": [
|
|
735
|
+
{
|
|
736
|
+
"field": "TemplateText",
|
|
737
|
+
"pattern": "@file:{Name}.template.md"
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
"field": "SQLQuery",
|
|
741
|
+
"pattern": "@file:queries/{CategoryName}/{Name}.sql"
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
Supported placeholders:
|
|
747
|
+
- `{Name}` - The entity's name field value
|
|
748
|
+
- `{ID}` - The entity's primary key
|
|
749
|
+
- `{FieldName}` - The field being externalized
|
|
750
|
+
- `{AnyFieldName}` - Any field from the entity record
|
|
751
|
+
|
|
752
|
+
All values are sanitized for filesystem compatibility (lowercase, spaces to hyphens, special characters removed).
|
|
753
|
+
|
|
441
754
|
### Nested Related Entities
|
|
442
755
|
Support for multiple levels of nesting:
|
|
443
756
|
```json
|