@memberjunction/metadata-sync 2.53.0 → 2.55.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 +78 -0
- package/dist/commands/push/index.js +9 -11
- package/dist/commands/push/index.js.map +1 -1
- package/dist/lib/sync-engine.d.ts +28 -1
- package/dist/lib/sync-engine.js +72 -4
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/services/ValidationService.d.ts +13 -0
- package/dist/services/ValidationService.js +80 -0
- package/dist/services/ValidationService.js.map +1 -1
- package/oclif.manifest.json +25 -25
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -756,6 +756,84 @@ Support environment-specific values:
|
|
|
756
756
|
- `@env:VARIABLE_NAME`
|
|
757
757
|
- Useful for different environments (dev/staging/prod)
|
|
758
758
|
|
|
759
|
+
### {@include} References in Files (NEW)
|
|
760
|
+
Enable content composition within non-JSON files (like .md, .html, .txt) using JSDoc-style include syntax:
|
|
761
|
+
- Pattern: `{@include path/to/file.ext}`
|
|
762
|
+
- Supports relative paths from the containing file
|
|
763
|
+
- Recursive includes (includes within includes)
|
|
764
|
+
- Circular reference detection prevents infinite loops
|
|
765
|
+
- Works seamlessly with `@file:` references
|
|
766
|
+
|
|
767
|
+
#### How It Works
|
|
768
|
+
When a JSON metadata file uses `@file:` to reference an external file, the MetadataSync tool:
|
|
769
|
+
1. Loads the referenced file
|
|
770
|
+
2. Scans for `{@include}` patterns
|
|
771
|
+
3. Recursively resolves all includes
|
|
772
|
+
4. Returns the fully composed content
|
|
773
|
+
|
|
774
|
+
#### Example Usage
|
|
775
|
+
```markdown
|
|
776
|
+
# My Prompt Template
|
|
777
|
+
|
|
778
|
+
## System Instructions
|
|
779
|
+
{@include ./shared/system-instructions.md}
|
|
780
|
+
|
|
781
|
+
## Context
|
|
782
|
+
{@include ../common/context-header.md}
|
|
783
|
+
|
|
784
|
+
## Task
|
|
785
|
+
Please analyze the following...
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
#### Complex Example with Nested Includes
|
|
789
|
+
Directory structure:
|
|
790
|
+
```
|
|
791
|
+
prompts/
|
|
792
|
+
├── customer-service/
|
|
793
|
+
│ ├── greeting.json # Uses @file:greeting.md
|
|
794
|
+
│ ├── greeting.md # Contains {@include} references
|
|
795
|
+
│ └── shared/
|
|
796
|
+
│ ├── tone.md
|
|
797
|
+
│ └── guidelines.md
|
|
798
|
+
└── common/
|
|
799
|
+
├── company-info.md
|
|
800
|
+
└── legal-disclaimer.md
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
greeting.json:
|
|
804
|
+
```json
|
|
805
|
+
{
|
|
806
|
+
"fields": {
|
|
807
|
+
"Name": "Customer Greeting",
|
|
808
|
+
"Prompt": "@file:greeting.md"
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
greeting.md:
|
|
814
|
+
```markdown
|
|
815
|
+
# Customer Service Greeting
|
|
816
|
+
|
|
817
|
+
{@include ./shared/tone.md}
|
|
818
|
+
|
|
819
|
+
## Guidelines
|
|
820
|
+
{@include ./shared/guidelines.md}
|
|
821
|
+
|
|
822
|
+
## Company Information
|
|
823
|
+
{@include ../common/company-info.md}
|
|
824
|
+
|
|
825
|
+
## Legal
|
|
826
|
+
{@include ../common/legal-disclaimer.md}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
The final content pushed to the database will have all includes fully resolved.
|
|
830
|
+
|
|
831
|
+
Benefits:
|
|
832
|
+
- **DRY Principle**: Share common content across multiple files
|
|
833
|
+
- **Maintainability**: Update shared content in one place
|
|
834
|
+
- **Flexibility**: Build complex documents from modular parts
|
|
835
|
+
- **Validation**: Automatic checking of included file existence and circular references
|
|
836
|
+
|
|
759
837
|
### @template: References (NEW)
|
|
760
838
|
Enable JSON template composition for reusable configurations:
|
|
761
839
|
|
|
@@ -515,7 +515,7 @@ class Push extends core_1.Command {
|
|
|
515
515
|
this.log(`Processing ${jsonFiles.length} records in ${path_1.default.relative(process.cwd(), entityDir) || '.'}`);
|
|
516
516
|
}
|
|
517
517
|
// First, process all JSON files in this directory
|
|
518
|
-
await this.processJsonFiles(jsonFiles, entityDir, entityConfig, syncEngine, flags, result, fileBackupManager);
|
|
518
|
+
await this.processJsonFiles(jsonFiles, entityDir, entityConfig, syncEngine, flags, result, fileBackupManager, syncConfig);
|
|
519
519
|
// Then, recursively process subdirectories
|
|
520
520
|
const entries = await fs_extra_1.default.readdir(entityDir, { withFileTypes: true });
|
|
521
521
|
for (const entry of entries) {
|
|
@@ -568,7 +568,7 @@ class Push extends core_1.Command {
|
|
|
568
568
|
}
|
|
569
569
|
return result;
|
|
570
570
|
}
|
|
571
|
-
async processJsonFiles(jsonFiles, entityDir, entityConfig, syncEngine, flags, result, fileBackupManager) {
|
|
571
|
+
async processJsonFiles(jsonFiles, entityDir, entityConfig, syncEngine, flags, result, fileBackupManager, syncConfig) {
|
|
572
572
|
if (jsonFiles.length === 0) {
|
|
573
573
|
return;
|
|
574
574
|
}
|
|
@@ -595,7 +595,7 @@ class Push extends core_1.Command {
|
|
|
595
595
|
const recordData = records[i];
|
|
596
596
|
// Process the record
|
|
597
597
|
const recordLineNumber = lineNumbers.get(i); // Get line number for this array index
|
|
598
|
-
const pushResult = await this.pushRecord(recordData, entityConfig.entity, path_1.default.dirname(filePath), file, defaults, syncEngine, flags['dry-run'], flags.verbose, isArray ? i : undefined, fileBackupManager, recordLineNumber);
|
|
598
|
+
const pushResult = await this.pushRecord(recordData, entityConfig.entity, path_1.default.dirname(filePath), file, defaults, syncEngine, flags['dry-run'], flags.verbose, isArray ? i : undefined, fileBackupManager, recordLineNumber, syncConfig);
|
|
599
599
|
if (!flags['dry-run']) {
|
|
600
600
|
// Don't count duplicates in stats
|
|
601
601
|
if (!pushResult.isDuplicate) {
|
|
@@ -643,7 +643,7 @@ class Push extends core_1.Command {
|
|
|
643
643
|
spinner.stop();
|
|
644
644
|
}
|
|
645
645
|
}
|
|
646
|
-
async pushRecord(recordData, entityName, baseDir, fileName, defaults, syncEngine, dryRun, verbose = false, arrayIndex, fileBackupManager, lineNumber) {
|
|
646
|
+
async pushRecord(recordData, entityName, baseDir, fileName, defaults, syncEngine, dryRun, verbose = false, arrayIndex, fileBackupManager, lineNumber, syncConfig) {
|
|
647
647
|
// Load or create entity
|
|
648
648
|
let entity = null;
|
|
649
649
|
let isNew = false;
|
|
@@ -654,8 +654,7 @@ class Push extends core_1.Command {
|
|
|
654
654
|
const pkDisplay = Object.entries(recordData.primaryKey)
|
|
655
655
|
.map(([key, value]) => `${key}=${value}`)
|
|
656
656
|
.join(', ');
|
|
657
|
-
//
|
|
658
|
-
const syncConfig = await (0, config_1.loadSyncConfig)(config_manager_1.configManager.getOriginalCwd());
|
|
657
|
+
// Use passed sync config to check autoCreateMissingRecords setting
|
|
659
658
|
const autoCreate = syncConfig?.push?.autoCreateMissingRecords ?? false;
|
|
660
659
|
if (!autoCreate) {
|
|
661
660
|
const fileRef = lineNumber ? `${fileName}:${lineNumber}` : fileName;
|
|
@@ -787,7 +786,7 @@ class Push extends core_1.Command {
|
|
|
787
786
|
const fullFilePath = path_1.default.join(baseDir, fileName);
|
|
788
787
|
relatedStats = await this.processRelatedEntities(recordData.relatedEntities, entity, entity, // root is same as parent for top level
|
|
789
788
|
baseDir, syncEngine, verbose, fileBackupManager, 1, // indentLevel
|
|
790
|
-
fullFilePath, arrayIndex);
|
|
789
|
+
fullFilePath, arrayIndex, syncConfig);
|
|
791
790
|
}
|
|
792
791
|
// Update the local file with new primary key if created
|
|
793
792
|
if (isNew) {
|
|
@@ -817,7 +816,7 @@ class Push extends core_1.Command {
|
|
|
817
816
|
}
|
|
818
817
|
return { isNew, wasActuallyUpdated, isDuplicate, relatedStats };
|
|
819
818
|
}
|
|
820
|
-
async processRelatedEntities(relatedEntities, parentEntity, rootEntity, baseDir, syncEngine, verbose = false, fileBackupManager, indentLevel = 1, parentFilePath, parentArrayIndex) {
|
|
819
|
+
async processRelatedEntities(relatedEntities, parentEntity, rootEntity, baseDir, syncEngine, verbose = false, fileBackupManager, indentLevel = 1, parentFilePath, parentArrayIndex, syncConfig) {
|
|
821
820
|
const indent = ' '.repeat(indentLevel);
|
|
822
821
|
const stats = { created: 0, updated: 0, unchanged: 0 };
|
|
823
822
|
for (const [entityName, records] of Object.entries(relatedEntities)) {
|
|
@@ -836,8 +835,7 @@ class Push extends core_1.Command {
|
|
|
836
835
|
const pkDisplay = Object.entries(relatedRecord.primaryKey)
|
|
837
836
|
.map(([key, value]) => `${key}=${value}`)
|
|
838
837
|
.join(', ');
|
|
839
|
-
//
|
|
840
|
-
const syncConfig = await (0, config_1.loadSyncConfig)(config_manager_1.configManager.getOriginalCwd());
|
|
838
|
+
// Use passed sync config to check autoCreateMissingRecords setting
|
|
841
839
|
const autoCreate = syncConfig?.push?.autoCreateMissingRecords ?? false;
|
|
842
840
|
if (!autoCreate) {
|
|
843
841
|
const fileRef = parentFilePath ? path_1.default.relative(config_manager_1.configManager.getOriginalCwd(), parentFilePath) : 'unknown';
|
|
@@ -991,7 +989,7 @@ class Push extends core_1.Command {
|
|
|
991
989
|
}
|
|
992
990
|
// Process nested related entities if any
|
|
993
991
|
if (relatedRecord.relatedEntities) {
|
|
994
|
-
const nestedStats = await this.processRelatedEntities(relatedRecord.relatedEntities, entity, rootEntity, baseDir, syncEngine, verbose, fileBackupManager, indentLevel + 1, parentFilePath, parentArrayIndex);
|
|
992
|
+
const nestedStats = await this.processRelatedEntities(relatedRecord.relatedEntities, entity, rootEntity, baseDir, syncEngine, verbose, fileBackupManager, indentLevel + 1, parentFilePath, parentArrayIndex, syncConfig);
|
|
995
993
|
// Accumulate nested stats
|
|
996
994
|
stats.created += nestedStats.created;
|
|
997
995
|
stats.updated += nestedStats.updated;
|