@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 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
- // Load sync config to check autoCreateMissingRecords setting
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
- // Load sync config to check autoCreateMissingRecords setting
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;