@memberjunction/metadata-sync 2.46.0 → 2.47.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/watch/index.ts"],"names":[],"mappings":";;;;;AAAA,sCAA6C;AAC7C,wDAA0B;AAC1B,gDAAwB;AACxB,wDAAgC;AAChC,8DAA8B;AAC9B,yCAA8E;AAC9E,uDAA+D;AAC/D,6DAAoG;AAGpG,MAAqB,KAAM,SAAQ,cAAO;IACxC,MAAM,CAAC,WAAW,GAAG,2DAA2D,CAAC;IAEjF,MAAM,CAAC,QAAQ,GAAG;QAChB,qCAAqC;QACrC,wDAAwD;KACzD,CAAC;IAEF,MAAM,CAAC,KAAK,GAAG;QACb,GAAG,EAAE,YAAK,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC;KACzE,CAAC;IAEM,UAAU,CAAc;IACxB,UAAU,CAAM;IAChB,cAAc,GAAgC,IAAI,GAAG,EAAE,CAAC;IAEhE,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAA,qBAAG,GAAE,CAAC;QAEtB,IAAI,CAAC;YACH,sBAAsB;YACtB,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,IAAA,qBAAY,GAAE,CAAC;YAChC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,IAAI,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;YAClF,CAAC;YAED,IAAI,CAAC,UAAU,GAAG,MAAM,IAAA,uBAAc,EAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YAEtD,2BAA2B;YAC3B,MAAM,QAAQ,GAAG,MAAM,IAAA,mCAAkB,EAAC,QAAQ,CAAC,CAAC;YAEpD,yBAAyB;YACzB,IAAI,CAAC,UAAU,GAAG,IAAI,wBAAU,CAAC,IAAA,8BAAa,GAAE,CAAC,CAAC;YAClD,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;YACnC,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;YAExC,mCAAmC;YACnC,MAAM,UAAU,GAAG,IAAA,sCAAqB,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YAEnE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;YAC5C,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,YAAY,UAAU,CAAC,MAAM,WAAW,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,cAAc,CAAC,CAAC;YAEtH,kBAAkB;YAClB,MAAM,QAAQ,GAAyB,EAAE,CAAC;YAE1C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,MAAM,YAAY,GAAG,MAAM,IAAA,yBAAgB,EAAC,SAAS,CAAC,CAAC;gBACvD,IAAI,CAAC,YAAY,EAAE,CAAC;oBAClB,IAAI,CAAC,IAAI,CAAC,YAAY,SAAS,kCAAkC,CAAC,CAAC;oBACnE,SAAS;gBACX,CAAC;gBAED,IAAI,CAAC,GAAG,CAAC,YAAY,YAAY,CAAC,MAAM,OAAO,SAAS,EAAE,CAAC,CAAC;gBAE5D,0CAA0C;gBAC1C,MAAM,QAAQ,GAAG;oBACf,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,WAAW,IAAI,QAAQ,CAAC;oBAC1D,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC;oBAC/B,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;oBAChC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC;oBACjC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC;oBACnC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;iBACjC,CAAC;gBAEF,MAAM,OAAO,GAAG;oBACd,oBAAoB;oBACpB,YAAY;oBACZ,kBAAkB;oBAClB,oBAAoB;oBACpB,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,cAAc,IAAI,EAAE,CAAC;iBAClD,CAAC;gBAEF,MAAM,OAAO,GAAG,kBAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE;oBACvC,OAAO;oBACP,UAAU,EAAE,IAAI;oBAChB,aAAa,EAAE,IAAI;iBACpB,CAAC,CAAC;gBAEH,OAAO;qBACJ,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;qBAC1F,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;qBAC/F,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;gBAEnG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzB,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;YAE5C,qBAAqB;YACrB,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAEvB,kBAAkB;YAClB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACxB,IAAI,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;gBACnC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QAEL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAC7B,IAAI,CAAC,KAAK,CAAC,KAAc,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAC5B,QAAgB,EAChB,KAAa,EACb,SAAiB,EACjB,YAAiB;QAEjB,gCAAgC;QAChC,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,aAAa,EAAE,CAAC;YAClB,YAAY,CAAC,aAAa,CAAC,CAAC;QAC9B,CAAC;QAED,yBAAyB;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,UAAU,IAAI,IAAI,CAAC;QAC9D,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YAClC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAErC,IAAI,CAAC;gBACH,MAAM,YAAY,GAAG,cAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;gBACxD,IAAI,CAAC,GAAG,CAAC,UAAU,KAAK,KAAK,YAAY,EAAE,CAAC,CAAC;gBAE7C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBACxB,kBAAkB;oBAClB,IAAI,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;gBAC/E,CAAC;qBAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBACtC,0BAA0B;oBAC1B,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;gBAC7D,CAAC;qBAAM,CAAC;oBACN,8BAA8B;oBAC9B,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;gBACjE,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,kBAAkB,QAAQ,KAAM,KAAa,CAAC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC;YAC9E,CAAC;QACH,CAAC,EAAE,UAAU,CAAC,CAAC;QAEf,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAEO,KAAK,CAAC,YAAY,CACxB,QAAgB,EAChB,SAAiB,EACjB,YAAiB;QAEjB,MAAM,UAAU,GAAe,MAAM,kBAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE3D,iBAAiB;QACjB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAE7E,wBAAwB;QACxB,IAAI,MAAM,GAAsB,IAAI,CAAC;QACrC,IAAI,KAAK,GAAG,KAAK,CAAC;QAElB,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;YAC1B,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;QACxF,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,aAAa;YACb,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACvE,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;QAED,uBAAuB;QACvB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtD,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;gBACnB,MAAc,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;YACjC,CAAC;QACH,CAAC;QAED,sBAAsB;QACtB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/D,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;gBACpB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,EAAE,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7F,MAAc,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC;YAC1C,CAAC;QACH,CAAC;QAED,kBAAkB;QAClB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC;YAC1E,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,YAAY,CAAC,MAAM,SAAS,CAAC,CAAC;QAExF,wDAAwD;QACxD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACtE,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,aAAa,GAAwB,EAAE,CAAC;gBAC9C,KAAK,MAAM,EAAE,IAAI,UAAU,CAAC,WAAW,EAAE,CAAC;oBACxC,aAAa,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;gBAC/C,CAAC;gBACD,UAAU,CAAC,UAAU,GAAG,aAAa,CAAC;gBAEtC,uBAAuB;gBACvB,UAAU,CAAC,IAAI,GAAG;oBAChB,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACtC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC;iBAC/D,CAAC;gBAEF,qBAAqB;gBACrB,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAC5B,QAAgB,EAChB,SAAiB,EACjB,YAAiB;QAEjB,mCAAmC;QACnC,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACtB,6BAA6B;YAC7B,MAAM,YAAY,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;YACxC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,CAAC;YAErE,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACtC,yDAAyD;gBACzD,MAAM,UAAU,GAAe,MAAM,kBAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;gBAC/D,UAAU,CAAC,IAAI,GAAG;oBAChB,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACtC,QAAQ,EAAE,UAAU,CAAC,IAAI,EAAE,QAAQ,IAAI,EAAE;iBAC1C,CAAC;gBACF,MAAM,kBAAE,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;gBAE5D,IAAI,CAAC,GAAG,CAAC,6BAA6B,YAAY,8BAA8B,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;IACH,CAAC;;AAtPH,wBAuPC","sourcesContent":["import { Command, Flags } from '@oclif/core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport chokidar from 'chokidar';\nimport ora from 'ora-classic';\nimport { loadMJConfig, loadSyncConfig, loadEntityConfig } from '../../config';\nimport { SyncEngine, RecordData } from '../../lib/sync-engine';\nimport { initializeProvider, findEntityDirectories, getSystemUser } from '../../lib/provider-utils';\nimport { BaseEntity } from '@memberjunction/core';\n\nexport default class Watch extends Command {\n static description = 'Watch for file changes and automatically push to database';\n \n static examples = [\n `<%= config.bin %> <%= command.id %>`,\n `<%= config.bin %> <%= command.id %> --dir=\"ai-prompts\"`,\n ];\n \n static flags = {\n dir: Flags.string({ description: 'Specific entity directory to watch' }),\n };\n \n private syncEngine!: SyncEngine;\n private syncConfig: any;\n private debounceTimers: Map<string, NodeJS.Timeout> = new Map();\n \n async run(): Promise<void> {\n const { flags } = await this.parse(Watch);\n const spinner = ora();\n \n try {\n // Load configurations\n spinner.start('Loading configuration');\n const mjConfig = loadMJConfig();\n if (!mjConfig) {\n this.error('No mj.config.cjs found in current directory or parent directories');\n }\n \n this.syncConfig = await loadSyncConfig(process.cwd());\n \n // Initialize data provider\n const provider = await initializeProvider(mjConfig);\n \n // Initialize sync engine\n this.syncEngine = new SyncEngine(getSystemUser());\n await this.syncEngine.initialize();\n spinner.succeed('Configuration loaded');\n \n // Find entity directories to watch\n const entityDirs = findEntityDirectories(process.cwd(), flags.dir);\n \n if (entityDirs.length === 0) {\n this.error('No entity directories found');\n }\n \n this.log(`Watching ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} for changes`);\n \n // Set up watchers\n const watchers: chokidar.FSWatcher[] = [];\n \n for (const entityDir of entityDirs) {\n const entityConfig = await loadEntityConfig(entityDir);\n if (!entityConfig) {\n this.warn(`Skipping ${entityDir} - no valid entity configuration`);\n continue;\n }\n \n this.log(`Watching ${entityConfig.entity} in ${entityDir}`);\n \n // Watch for JSON files and external files\n const patterns = [\n path.join(entityDir, entityConfig.filePattern || '*.json'),\n path.join(entityDir, '**/*.md'),\n path.join(entityDir, '**/*.txt'),\n path.join(entityDir, '**/*.html'),\n path.join(entityDir, '**/*.liquid'),\n path.join(entityDir, '**/*.sql')\n ];\n \n const ignored = [\n '**/node_modules/**',\n '**/.git/**',\n '**/.mj-sync.json',\n '**/.mj-folder.json',\n ...(this.syncConfig?.watch?.ignorePatterns || [])\n ];\n \n const watcher = chokidar.watch(patterns, {\n ignored,\n persistent: true,\n ignoreInitial: true\n });\n \n watcher\n .on('add', (filePath) => this.handleFileChange(filePath, 'added', entityDir, entityConfig))\n .on('change', (filePath) => this.handleFileChange(filePath, 'changed', entityDir, entityConfig))\n .on('unlink', (filePath) => this.handleFileChange(filePath, 'deleted', entityDir, entityConfig));\n \n watchers.push(watcher);\n }\n \n this.log('\\nPress Ctrl+C to stop watching');\n \n // Keep process alive\n process.stdin.resume();\n \n // Cleanup on exit\n process.on('SIGINT', () => {\n this.log('\\nStopping watchers...');\n watchers.forEach(w => w.close());\n process.exit(0);\n });\n \n } catch (error) {\n spinner.fail('Watch failed');\n this.error(error as Error);\n }\n }\n \n private async handleFileChange(\n filePath: string,\n event: string,\n entityDir: string,\n entityConfig: any\n ): Promise<void> {\n // Clear existing debounce timer\n const existingTimer = this.debounceTimers.get(filePath);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n \n // Set new debounce timer\n const debounceMs = this.syncConfig?.watch?.debounceMs || 1000;\n const timer = setTimeout(async () => {\n this.debounceTimers.delete(filePath);\n \n try {\n const relativePath = path.relative(entityDir, filePath);\n this.log(`\\nFile ${event}: ${relativePath}`);\n \n if (event === 'deleted') {\n // Handle deletion\n this.log('File deletion detected - manual database cleanup may be required');\n } else if (filePath.endsWith('.json')) {\n // Handle JSON file change\n await this.syncJsonFile(filePath, entityDir, entityConfig);\n } else {\n // Handle external file change\n await this.syncExternalFile(filePath, entityDir, entityConfig);\n }\n } catch (error) {\n this.warn(`Failed to sync ${filePath}: ${(error as any).message || error}`);\n }\n }, debounceMs);\n \n this.debounceTimers.set(filePath, timer);\n }\n \n private async syncJsonFile(\n filePath: string,\n entityDir: string,\n entityConfig: any\n ): Promise<void> {\n const recordData: RecordData = await fs.readJson(filePath);\n \n // Build defaults\n const defaults = await this.syncEngine.buildDefaults(filePath, entityConfig);\n \n // Load or create entity\n let entity: BaseEntity | null = null;\n let isNew = false;\n \n if (recordData.primaryKey) {\n entity = await this.syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);\n }\n \n if (!entity) {\n // New record\n entity = await this.syncEngine.createEntityObject(entityConfig.entity);\n entity.NewRecord();\n isNew = true;\n }\n \n // Apply defaults first\n for (const [field, value] of Object.entries(defaults)) {\n if (field in entity) {\n (entity as any)[field] = value;\n }\n }\n \n // Apply record fields\n for (const [field, value] of Object.entries(recordData.fields)) {\n if (field in entity) {\n const processedValue = await this.syncEngine.processFieldValue(value, path.dirname(filePath));\n (entity as any)[field] = processedValue;\n }\n }\n \n // Save the record\n const saved = await entity.Save();\n if (!saved) {\n const errors = entity.LatestResult?.Errors?.join(', ') || 'Unknown error';\n throw new Error(`Failed to save record: ${errors}`);\n }\n \n this.log(`Successfully ${isNew ? 'created' : 'updated'} ${entityConfig.entity} record`);\n \n // Update the local file with new primary key if created\n if (isNew) {\n const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);\n if (entityInfo) {\n const newPrimaryKey: Record<string, any> = {};\n for (const pk of entityInfo.PrimaryKeys) {\n newPrimaryKey[pk.Name] = entity.Get(pk.Name);\n }\n recordData.primaryKey = newPrimaryKey;\n \n // Update sync metadata\n recordData.sync = {\n lastModified: new Date().toISOString(),\n checksum: this.syncEngine.calculateChecksum(recordData.fields)\n };\n \n // Write back to file\n await fs.writeJson(filePath, recordData, { spaces: 2 });\n }\n }\n }\n \n private async syncExternalFile(\n filePath: string,\n entityDir: string,\n entityConfig: any\n ): Promise<void> {\n // Find the corresponding JSON file\n const fileName = path.basename(filePath);\n const parts = fileName.split('.');\n \n if (parts.length >= 3) {\n // Format: uuid.fieldname.ext\n const jsonFileName = `${parts[0]}.json`;\n const fieldName = parts[1];\n const jsonFilePath = path.join(path.dirname(filePath), jsonFileName);\n \n if (await fs.pathExists(jsonFilePath)) {\n // Update the JSON file's sync metadata to trigger a sync\n const recordData: RecordData = await fs.readJson(jsonFilePath);\n recordData.sync = {\n lastModified: new Date().toISOString(),\n checksum: recordData.sync?.checksum || ''\n };\n await fs.writeJson(jsonFilePath, recordData, { spaces: 2 });\n \n this.log(`Updated sync metadata for ${jsonFileName} due to external file change`);\n }\n }\n }\n}"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/watch/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { Command, Flags } from '@oclif/core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport chokidar from 'chokidar';\nimport ora from 'ora-classic';\nimport { loadMJConfig, loadSyncConfig, loadEntityConfig } from '../../config';\nimport { SyncEngine, RecordData } from '../../lib/sync-engine';\nimport { initializeProvider, findEntityDirectories, getSystemUser } from '../../lib/provider-utils';\nimport { BaseEntity } from '@memberjunction/core';\n\nexport default class Watch extends Command {\n static description = 'Watch for file changes and automatically push to database';\n \n static examples = [\n `<%= config.bin %> <%= command.id %>`,\n `<%= config.bin %> <%= command.id %> --dir=\"ai-prompts\"`,\n ];\n \n static flags = {\n dir: Flags.string({ description: 'Specific entity directory to watch' }),\n };\n \n private syncEngine!: SyncEngine;\n private syncConfig: any;\n private debounceTimers: Map<string, NodeJS.Timeout> = new Map();\n \n async run(): Promise<void> {\n const { flags } = await this.parse(Watch);\n const spinner = ora();\n \n try {\n // Load configurations\n spinner.start('Loading configuration');\n const mjConfig = loadMJConfig();\n if (!mjConfig) {\n this.error('No mj.config.cjs found in current directory or parent directories');\n }\n \n this.syncConfig = await loadSyncConfig(process.cwd());\n \n // Initialize data provider\n const provider = await initializeProvider(mjConfig);\n \n // Initialize sync engine\n this.syncEngine = new SyncEngine(getSystemUser());\n await this.syncEngine.initialize();\n spinner.succeed('Configuration loaded');\n \n // Find entity directories to watch\n const entityDirs = findEntityDirectories(process.cwd(), flags.dir);\n \n if (entityDirs.length === 0) {\n this.error('No entity directories found');\n }\n \n this.log(`Watching ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} for changes`);\n \n // Set up watchers\n const watchers: chokidar.FSWatcher[] = [];\n \n for (const entityDir of entityDirs) {\n const entityConfig = await loadEntityConfig(entityDir);\n if (!entityConfig) {\n this.warn(`Skipping ${entityDir} - no valid entity configuration`);\n continue;\n }\n \n this.log(`Watching ${entityConfig.entity} in ${entityDir}`);\n \n // Watch for JSON files and external files\n const patterns = [\n path.join(entityDir, entityConfig.filePattern || '**/*.json'),\n path.join(entityDir, '**/*.md'),\n path.join(entityDir, '**/*.txt'),\n path.join(entityDir, '**/*.html'),\n path.join(entityDir, '**/*.liquid'),\n path.join(entityDir, '**/*.sql')\n ];\n \n const ignored = [\n '**/node_modules/**',\n '**/.git/**',\n '**/.mj-sync.json',\n '**/.mj-folder.json',\n ...(this.syncConfig?.watch?.ignorePatterns || [])\n ];\n \n const watcher = chokidar.watch(patterns, {\n ignored,\n persistent: true,\n ignoreInitial: true\n });\n \n watcher\n .on('add', (filePath) => this.handleFileChange(filePath, 'added', entityDir, entityConfig))\n .on('change', (filePath) => this.handleFileChange(filePath, 'changed', entityDir, entityConfig))\n .on('unlink', (filePath) => this.handleFileChange(filePath, 'deleted', entityDir, entityConfig));\n \n watchers.push(watcher);\n }\n \n this.log('\\nPress Ctrl+C to stop watching');\n \n // Keep process alive\n process.stdin.resume();\n \n // Cleanup on exit\n process.on('SIGINT', () => {\n this.log('\\nStopping watchers...');\n watchers.forEach(w => w.close());\n process.exit(0);\n });\n \n } catch (error) {\n spinner.fail('Watch failed');\n this.error(error as Error);\n }\n }\n \n private async handleFileChange(\n filePath: string,\n event: string,\n entityDir: string,\n entityConfig: any\n ): Promise<void> {\n // Clear existing debounce timer\n const existingTimer = this.debounceTimers.get(filePath);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n \n // Set new debounce timer\n const debounceMs = this.syncConfig?.watch?.debounceMs || 1000;\n const timer = setTimeout(async () => {\n this.debounceTimers.delete(filePath);\n \n try {\n const relativePath = path.relative(entityDir, filePath);\n this.log(`\\nFile ${event}: ${relativePath}`);\n \n if (event === 'deleted') {\n // Handle deletion\n this.log('File deletion detected - manual database cleanup may be required');\n } else if (filePath.endsWith('.json')) {\n // Handle JSON file change\n await this.syncJsonFile(filePath, entityDir, entityConfig);\n } else {\n // Handle external file change\n await this.syncExternalFile(filePath, entityDir, entityConfig);\n }\n } catch (error) {\n this.warn(`Failed to sync ${filePath}: ${(error as any).message || error}`);\n }\n }, debounceMs);\n \n this.debounceTimers.set(filePath, timer);\n }\n \n private async syncJsonFile(\n filePath: string,\n entityDir: string,\n entityConfig: any\n ): Promise<void> {\n const recordData: RecordData = await fs.readJson(filePath);\n \n // Build defaults\n const defaults = await this.syncEngine.buildDefaults(filePath, entityConfig);\n \n // Load or create entity\n let entity: BaseEntity | null = null;\n let isNew = false;\n \n if (recordData.primaryKey) {\n entity = await this.syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);\n }\n \n if (!entity) {\n // New record\n entity = await this.syncEngine.createEntityObject(entityConfig.entity);\n entity.NewRecord();\n isNew = true;\n }\n \n // Apply defaults first\n for (const [field, value] of Object.entries(defaults)) {\n if (field in entity) {\n (entity as any)[field] = value;\n }\n }\n \n // Apply record fields\n for (const [field, value] of Object.entries(recordData.fields)) {\n if (field in entity) {\n const processedValue = await this.syncEngine.processFieldValue(value, path.dirname(filePath));\n (entity as any)[field] = processedValue;\n }\n }\n \n // Save the record\n const saved = await entity.Save();\n if (!saved) {\n const errors = entity.LatestResult?.Errors?.join(', ') || 'Unknown error';\n throw new Error(`Failed to save record: ${errors}`);\n }\n \n this.log(`Successfully ${isNew ? 'created' : 'updated'} ${entityConfig.entity} record`);\n \n // Update the local file with new primary key if created\n if (isNew) {\n const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);\n if (entityInfo) {\n const newPrimaryKey: Record<string, any> = {};\n for (const pk of entityInfo.PrimaryKeys) {\n newPrimaryKey[pk.Name] = entity.Get(pk.Name);\n }\n recordData.primaryKey = newPrimaryKey;\n \n // Update sync metadata\n recordData.sync = {\n lastModified: new Date().toISOString(),\n checksum: this.syncEngine.calculateChecksum(recordData.fields)\n };\n \n // Write back to file\n await fs.writeJson(filePath, recordData, { spaces: 2 });\n }\n }\n }\n \n private async syncExternalFile(\n filePath: string,\n entityDir: string,\n entityConfig: any\n ): Promise<void> {\n // Find the corresponding JSON file\n const fileName = path.basename(filePath);\n const parts = fileName.split('.');\n \n if (parts.length >= 3) {\n // Format: uuid.fieldname.ext\n const jsonFileName = `${parts[0]}.json`;\n const fieldName = parts[1];\n const jsonFilePath = path.join(path.dirname(filePath), jsonFileName);\n \n if (await fs.pathExists(jsonFilePath)) {\n // Update the JSON file's sync metadata to trigger a sync\n const recordData: RecordData = await fs.readJson(jsonFilePath);\n recordData.sync = {\n lastModified: new Date().toISOString(),\n checksum: recordData.sync?.checksum || ''\n };\n await fs.writeJson(jsonFilePath, recordData, { spaces: 2 });\n \n this.log(`Updated sync metadata for ${jsonFileName} due to external file change`);\n }\n }\n }\n}"]}
package/dist/config.d.ts CHANGED
@@ -1,44 +1,185 @@
1
+ /**
2
+ * @fileoverview Configuration types and loaders for MetadataSync
3
+ * @module config
4
+ *
5
+ * This module defines configuration interfaces and provides utilities for loading
6
+ * various configuration files used by the MetadataSync tool. It supports:
7
+ * - MemberJunction database configuration (mj.config.cjs)
8
+ * - Sync configuration (.mj-sync.json)
9
+ * - Entity-specific configuration (.mj-sync.json with entity field)
10
+ * - Folder-level defaults (.mj-folder.json)
11
+ */
12
+ /**
13
+ * MemberJunction database configuration
14
+ *
15
+ * Defines connection parameters and settings for connecting to the MemberJunction
16
+ * database. Typically loaded from mj.config.cjs in the project root.
17
+ */
1
18
  export interface MJConfig {
19
+ /** Database server hostname or IP address */
2
20
  dbHost: string;
21
+ /** Database server port (defaults to 1433 for SQL Server) */
3
22
  dbPort?: number;
23
+ /** Database name to connect to */
4
24
  dbDatabase: string;
25
+ /** Database authentication username */
5
26
  dbUsername: string;
27
+ /** Database authentication password */
6
28
  dbPassword: string;
29
+ /** Whether to trust the server certificate (Y/N) */
7
30
  dbTrustServerCertificate?: string;
31
+ /** Whether to encrypt the connection (Y/N, auto-detected for Azure SQL) */
32
+ dbEncrypt?: string;
33
+ /** SQL Server instance name (for named instances) */
8
34
  dbInstanceName?: string;
35
+ /** Schema name for MemberJunction core tables (defaults to __mj) */
9
36
  mjCoreSchema?: string;
37
+ /** Allow additional properties for extensibility */
10
38
  [key: string]: any;
11
39
  }
40
+ /**
41
+ * Global sync configuration
42
+ *
43
+ * Defines settings that apply to the entire sync process, including push/pull
44
+ * behaviors and watch mode configuration. Stored in .mj-sync.json at the root.
45
+ */
12
46
  export interface SyncConfig {
47
+ /** Version of the sync configuration format */
13
48
  version: string;
49
+ /** Push command configuration */
14
50
  push?: {
51
+ /** Whether to validate records before pushing to database */
15
52
  validateBeforePush?: boolean;
53
+ /** Whether to require user confirmation before push */
16
54
  requireConfirmation?: boolean;
17
55
  };
56
+ /** Watch command configuration */
18
57
  watch?: {
58
+ /** Milliseconds to wait before processing file changes */
19
59
  debounceMs?: number;
60
+ /** File patterns to ignore during watch */
20
61
  ignorePatterns?: string[];
21
62
  };
22
63
  }
64
+ /**
65
+ * Configuration for related entity synchronization
66
+ *
67
+ * Defines how to pull and push related entities that have foreign key relationships
68
+ * with a parent entity. Supports nested relationships for deep object graphs.
69
+ */
23
70
  export interface RelatedEntityConfig {
71
+ /** Name of the related entity to sync */
24
72
  entity: string;
73
+ /** Field name that contains the foreign key reference to parent (e.g., "PromptID") */
25
74
  foreignKey: string;
75
+ /** Optional SQL filter to apply when pulling related records */
26
76
  filter?: string;
77
+ /** Nested related entities for deep object graphs */
27
78
  relatedEntities?: Record<string, RelatedEntityConfig>;
28
79
  }
80
+ /**
81
+ * Entity-specific configuration
82
+ *
83
+ * Defines settings for a specific entity type within a directory. Stored in
84
+ * .mj-sync.json files that contain an "entity" field. Supports defaults,
85
+ * file patterns, and related entity configuration.
86
+ */
29
87
  export interface EntityConfig {
88
+ /** Name of the entity this directory contains */
30
89
  entity: string;
90
+ /** Glob pattern for finding data files (defaults to "*.json") */
31
91
  filePattern?: string;
92
+ /** Default field values applied to all records in this directory */
32
93
  defaults?: Record<string, any>;
94
+ /** Pull command specific configuration */
33
95
  pull?: {
96
+ /** SQL filter to apply when pulling records from database */
34
97
  filter?: string;
98
+ /** Configuration for pulling related entities */
35
99
  relatedEntities?: Record<string, RelatedEntityConfig>;
36
100
  };
37
101
  }
102
+ /**
103
+ * Folder-level configuration
104
+ *
105
+ * Defines default values that cascade down to all subdirectories. Stored in
106
+ * .mj-folder.json files. Child folders can override parent defaults.
107
+ */
38
108
  export interface FolderConfig {
109
+ /** Default field values that apply to all entities in this folder and subfolders */
39
110
  defaults: Record<string, any>;
40
111
  }
112
+ /**
113
+ * Load MemberJunction configuration from the filesystem
114
+ *
115
+ * Searches for mj.config.cjs starting from the current directory and walking up
116
+ * the directory tree. Uses cosmiconfig for flexible configuration loading.
117
+ *
118
+ * @returns MJConfig object if found, null if not found or invalid
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const config = loadMJConfig();
123
+ * if (config) {
124
+ * console.log(`Connecting to ${config.dbHost}:${config.dbPort || 1433}`);
125
+ * }
126
+ * ```
127
+ */
41
128
  export declare function loadMJConfig(): MJConfig | null;
129
+ /**
130
+ * Load sync configuration from a directory
131
+ *
132
+ * Loads .mj-sync.json file from the specified directory. This file can contain
133
+ * either global sync configuration (no entity field) or entity-specific
134
+ * configuration (with entity field).
135
+ *
136
+ * @param dir - Directory path to load configuration from
137
+ * @returns Promise resolving to SyncConfig if found and valid, null otherwise
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * const syncConfig = await loadSyncConfig('/path/to/project');
142
+ * if (syncConfig?.push?.requireConfirmation) {
143
+ * // Show confirmation prompt
144
+ * }
145
+ * ```
146
+ */
42
147
  export declare function loadSyncConfig(dir: string): Promise<SyncConfig | null>;
148
+ /**
149
+ * Load entity-specific configuration from a directory
150
+ *
151
+ * Loads .mj-sync.json file that contains an "entity" field, indicating this
152
+ * directory contains data for a specific entity type. Returns null if the
153
+ * file doesn't exist or doesn't contain an entity field.
154
+ *
155
+ * @param dir - Directory path to load configuration from
156
+ * @returns Promise resolving to EntityConfig if found and valid, null otherwise
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const entityConfig = await loadEntityConfig('./ai-prompts');
161
+ * if (entityConfig) {
162
+ * console.log(`Directory contains ${entityConfig.entity} records`);
163
+ * }
164
+ * ```
165
+ */
43
166
  export declare function loadEntityConfig(dir: string): Promise<EntityConfig | null>;
167
+ /**
168
+ * Load folder-level configuration
169
+ *
170
+ * Loads .mj-folder.json file that contains default values to be applied to
171
+ * all entities in this folder and its subfolders. Used for cascading defaults
172
+ * in deep directory structures.
173
+ *
174
+ * @param dir - Directory path to load configuration from
175
+ * @returns Promise resolving to FolderConfig if found and valid, null otherwise
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * const folderConfig = await loadFolderConfig('./templates');
180
+ * if (folderConfig?.defaults) {
181
+ * // Apply folder defaults to records
182
+ * }
183
+ * ```
184
+ */
44
185
  export declare function loadFolderConfig(dir: string): Promise<FolderConfig | null>;
package/dist/config.js CHANGED
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ /**
3
+ * @fileoverview Configuration types and loaders for MetadataSync
4
+ * @module config
5
+ *
6
+ * This module defines configuration interfaces and provides utilities for loading
7
+ * various configuration files used by the MetadataSync tool. It supports:
8
+ * - MemberJunction database configuration (mj.config.cjs)
9
+ * - Sync configuration (.mj-sync.json)
10
+ * - Entity-specific configuration (.mj-sync.json with entity field)
11
+ * - Folder-level defaults (.mj-folder.json)
12
+ */
2
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
15
  };
@@ -7,6 +18,22 @@ exports.loadFolderConfig = exports.loadEntityConfig = exports.loadSyncConfig = e
7
18
  const cosmiconfig_1 = require("cosmiconfig");
8
19
  const path_1 = __importDefault(require("path"));
9
20
  const fs_extra_1 = __importDefault(require("fs-extra"));
21
+ /**
22
+ * Load MemberJunction configuration from the filesystem
23
+ *
24
+ * Searches for mj.config.cjs starting from the current directory and walking up
25
+ * the directory tree. Uses cosmiconfig for flexible configuration loading.
26
+ *
27
+ * @returns MJConfig object if found, null if not found or invalid
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const config = loadMJConfig();
32
+ * if (config) {
33
+ * console.log(`Connecting to ${config.dbHost}:${config.dbPort || 1433}`);
34
+ * }
35
+ * ```
36
+ */
10
37
  function loadMJConfig() {
11
38
  try {
12
39
  const explorer = (0, cosmiconfig_1.cosmiconfigSync)('mj');
@@ -22,6 +49,24 @@ function loadMJConfig() {
22
49
  }
23
50
  }
24
51
  exports.loadMJConfig = loadMJConfig;
52
+ /**
53
+ * Load sync configuration from a directory
54
+ *
55
+ * Loads .mj-sync.json file from the specified directory. This file can contain
56
+ * either global sync configuration (no entity field) or entity-specific
57
+ * configuration (with entity field).
58
+ *
59
+ * @param dir - Directory path to load configuration from
60
+ * @returns Promise resolving to SyncConfig if found and valid, null otherwise
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const syncConfig = await loadSyncConfig('/path/to/project');
65
+ * if (syncConfig?.push?.requireConfirmation) {
66
+ * // Show confirmation prompt
67
+ * }
68
+ * ```
69
+ */
25
70
  async function loadSyncConfig(dir) {
26
71
  const configPath = path_1.default.join(dir, '.mj-sync.json');
27
72
  if (await fs_extra_1.default.pathExists(configPath)) {
@@ -36,6 +81,24 @@ async function loadSyncConfig(dir) {
36
81
  return null;
37
82
  }
38
83
  exports.loadSyncConfig = loadSyncConfig;
84
+ /**
85
+ * Load entity-specific configuration from a directory
86
+ *
87
+ * Loads .mj-sync.json file that contains an "entity" field, indicating this
88
+ * directory contains data for a specific entity type. Returns null if the
89
+ * file doesn't exist or doesn't contain an entity field.
90
+ *
91
+ * @param dir - Directory path to load configuration from
92
+ * @returns Promise resolving to EntityConfig if found and valid, null otherwise
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const entityConfig = await loadEntityConfig('./ai-prompts');
97
+ * if (entityConfig) {
98
+ * console.log(`Directory contains ${entityConfig.entity} records`);
99
+ * }
100
+ * ```
101
+ */
39
102
  async function loadEntityConfig(dir) {
40
103
  const configPath = path_1.default.join(dir, '.mj-sync.json');
41
104
  if (await fs_extra_1.default.pathExists(configPath)) {
@@ -52,6 +115,24 @@ async function loadEntityConfig(dir) {
52
115
  return null;
53
116
  }
54
117
  exports.loadEntityConfig = loadEntityConfig;
118
+ /**
119
+ * Load folder-level configuration
120
+ *
121
+ * Loads .mj-folder.json file that contains default values to be applied to
122
+ * all entities in this folder and its subfolders. Used for cascading defaults
123
+ * in deep directory structures.
124
+ *
125
+ * @param dir - Directory path to load configuration from
126
+ * @returns Promise resolving to FolderConfig if found and valid, null otherwise
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const folderConfig = await loadFolderConfig('./templates');
131
+ * if (folderConfig?.defaults) {
132
+ * // Apply folder defaults to records
133
+ * }
134
+ * ```
135
+ */
55
136
  async function loadFolderConfig(dir) {
56
137
  const configPath = path_1.default.join(dir, '.mj-folder.json');
57
138
  if (await fs_extra_1.default.pathExists(configPath)) {
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;;;;AAAA,6CAA8C;AAC9C,gDAAwB;AACxB,wDAA0B;AA+C1B,SAAgB,YAAY;IAC1B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAA,6BAAe,EAAC,IAAI,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAE9C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAdD,oCAcC;AAEM,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEnD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAbD,wCAaC;AAEM,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEnD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YAC7C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAfD,4CAeC;AAEM,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IAErD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAbD,4CAaC","sourcesContent":["import { cosmiconfigSync } from 'cosmiconfig';\nimport path from 'path';\nimport fs from 'fs-extra';\n\nexport interface MJConfig {\n dbHost: string;\n dbPort?: number;\n dbDatabase: string;\n dbUsername: string;\n dbPassword: string;\n dbTrustServerCertificate?: string;\n dbInstanceName?: string;\n mjCoreSchema?: string;\n [key: string]: any; // Allow other properties\n}\n\nexport interface SyncConfig {\n version: string;\n push?: {\n validateBeforePush?: boolean;\n requireConfirmation?: boolean;\n };\n watch?: {\n debounceMs?: number;\n ignorePatterns?: string[];\n };\n}\n\nexport interface RelatedEntityConfig {\n entity: string;\n foreignKey: string; // Field that links to parent (e.g., \"PromptID\")\n filter?: string; // Additional filter\n relatedEntities?: Record<string, RelatedEntityConfig>; // Nested related entities\n}\n\nexport interface EntityConfig {\n entity: string;\n filePattern?: string;\n defaults?: Record<string, any>;\n pull?: {\n filter?: string; // Default filter for pulling records\n relatedEntities?: Record<string, RelatedEntityConfig>;\n };\n}\n\nexport interface FolderConfig {\n defaults: Record<string, any>;\n}\n\nexport function loadMJConfig(): MJConfig | null {\n try {\n const explorer = cosmiconfigSync('mj');\n const result = explorer.search(process.cwd());\n \n if (!result || !result.config) {\n throw new Error('No mj.config.cjs found');\n }\n \n return result.config;\n } catch (error) {\n console.error('Error loading MJ config:', error);\n return null;\n }\n}\n\nexport async function loadSyncConfig(dir: string): Promise<SyncConfig | null> {\n const configPath = path.join(dir, '.mj-sync.json');\n \n if (await fs.pathExists(configPath)) {\n try {\n return await fs.readJson(configPath);\n } catch (error) {\n console.error('Error loading sync config:', error);\n return null;\n }\n }\n \n return null;\n}\n\nexport async function loadEntityConfig(dir: string): Promise<EntityConfig | null> {\n const configPath = path.join(dir, '.mj-sync.json');\n \n if (await fs.pathExists(configPath)) {\n try {\n const config = await fs.readJson(configPath);\n if (config.entity) {\n return config;\n }\n } catch (error) {\n console.error('Error loading entity config:', error);\n }\n }\n \n return null;\n}\n\nexport async function loadFolderConfig(dir: string): Promise<FolderConfig | null> {\n const configPath = path.join(dir, '.mj-folder.json');\n \n if (await fs.pathExists(configPath)) {\n try {\n return await fs.readJson(configPath);\n } catch (error) {\n console.error('Error loading folder config:', error);\n return null;\n }\n }\n \n return null;\n}"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;;;;AAEH,6CAA8C;AAC9C,gDAAwB;AACxB,wDAA0B;AA2G1B;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,YAAY;IAC1B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAA,6BAAe,EAAC,IAAI,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAE9C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAdD,oCAcC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACI,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEnD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAbD,wCAaC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACI,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEnD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YAC7C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAfD,4CAeC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACI,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IAErD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAbD,4CAaC","sourcesContent":["/**\n * @fileoverview Configuration types and loaders for MetadataSync\n * @module config\n * \n * This module defines configuration interfaces and provides utilities for loading\n * various configuration files used by the MetadataSync tool. It supports:\n * - MemberJunction database configuration (mj.config.cjs)\n * - Sync configuration (.mj-sync.json)\n * - Entity-specific configuration (.mj-sync.json with entity field)\n * - Folder-level defaults (.mj-folder.json)\n */\n\nimport { cosmiconfigSync } from 'cosmiconfig';\nimport path from 'path';\nimport fs from 'fs-extra';\n\n/**\n * MemberJunction database configuration\n * \n * Defines connection parameters and settings for connecting to the MemberJunction\n * database. Typically loaded from mj.config.cjs in the project root.\n */\nexport interface MJConfig {\n /** Database server hostname or IP address */\n dbHost: string;\n /** Database server port (defaults to 1433 for SQL Server) */\n dbPort?: number;\n /** Database name to connect to */\n dbDatabase: string;\n /** Database authentication username */\n dbUsername: string;\n /** Database authentication password */\n dbPassword: string;\n /** Whether to trust the server certificate (Y/N) */\n dbTrustServerCertificate?: string;\n /** Whether to encrypt the connection (Y/N, auto-detected for Azure SQL) */\n dbEncrypt?: string;\n /** SQL Server instance name (for named instances) */\n dbInstanceName?: string;\n /** Schema name for MemberJunction core tables (defaults to __mj) */\n mjCoreSchema?: string;\n /** Allow additional properties for extensibility */\n [key: string]: any;\n}\n\n/**\n * Global sync configuration\n * \n * Defines settings that apply to the entire sync process, including push/pull\n * behaviors and watch mode configuration. Stored in .mj-sync.json at the root.\n */\nexport interface SyncConfig {\n /** Version of the sync configuration format */\n version: string;\n /** Push command configuration */\n push?: {\n /** Whether to validate records before pushing to database */\n validateBeforePush?: boolean;\n /** Whether to require user confirmation before push */\n requireConfirmation?: boolean;\n };\n /** Watch command configuration */\n watch?: {\n /** Milliseconds to wait before processing file changes */\n debounceMs?: number;\n /** File patterns to ignore during watch */\n ignorePatterns?: string[];\n };\n}\n\n/**\n * Configuration for related entity synchronization\n * \n * Defines how to pull and push related entities that have foreign key relationships\n * with a parent entity. Supports nested relationships for deep object graphs.\n */\nexport interface RelatedEntityConfig {\n /** Name of the related entity to sync */\n entity: string;\n /** Field name that contains the foreign key reference to parent (e.g., \"PromptID\") */\n foreignKey: string;\n /** Optional SQL filter to apply when pulling related records */\n filter?: string;\n /** Nested related entities for deep object graphs */\n relatedEntities?: Record<string, RelatedEntityConfig>;\n}\n\n/**\n * Entity-specific configuration\n * \n * Defines settings for a specific entity type within a directory. Stored in\n * .mj-sync.json files that contain an \"entity\" field. Supports defaults,\n * file patterns, and related entity configuration.\n */\nexport interface EntityConfig {\n /** Name of the entity this directory contains */\n entity: string;\n /** Glob pattern for finding data files (defaults to \"*.json\") */\n filePattern?: string;\n /** Default field values applied to all records in this directory */\n defaults?: Record<string, any>;\n /** Pull command specific configuration */\n pull?: {\n /** SQL filter to apply when pulling records from database */\n filter?: string;\n /** Configuration for pulling related entities */\n relatedEntities?: Record<string, RelatedEntityConfig>;\n };\n}\n\n/**\n * Folder-level configuration\n * \n * Defines default values that cascade down to all subdirectories. Stored in\n * .mj-folder.json files. Child folders can override parent defaults.\n */\nexport interface FolderConfig {\n /** Default field values that apply to all entities in this folder and subfolders */\n defaults: Record<string, any>;\n}\n\n/**\n * Load MemberJunction configuration from the filesystem\n * \n * Searches for mj.config.cjs starting from the current directory and walking up\n * the directory tree. Uses cosmiconfig for flexible configuration loading.\n * \n * @returns MJConfig object if found, null if not found or invalid\n * \n * @example\n * ```typescript\n * const config = loadMJConfig();\n * if (config) {\n * console.log(`Connecting to ${config.dbHost}:${config.dbPort || 1433}`);\n * }\n * ```\n */\nexport function loadMJConfig(): MJConfig | null {\n try {\n const explorer = cosmiconfigSync('mj');\n const result = explorer.search(process.cwd());\n \n if (!result || !result.config) {\n throw new Error('No mj.config.cjs found');\n }\n \n return result.config;\n } catch (error) {\n console.error('Error loading MJ config:', error);\n return null;\n }\n}\n\n/**\n * Load sync configuration from a directory\n * \n * Loads .mj-sync.json file from the specified directory. This file can contain\n * either global sync configuration (no entity field) or entity-specific\n * configuration (with entity field).\n * \n * @param dir - Directory path to load configuration from\n * @returns Promise resolving to SyncConfig if found and valid, null otherwise\n * \n * @example\n * ```typescript\n * const syncConfig = await loadSyncConfig('/path/to/project');\n * if (syncConfig?.push?.requireConfirmation) {\n * // Show confirmation prompt\n * }\n * ```\n */\nexport async function loadSyncConfig(dir: string): Promise<SyncConfig | null> {\n const configPath = path.join(dir, '.mj-sync.json');\n \n if (await fs.pathExists(configPath)) {\n try {\n return await fs.readJson(configPath);\n } catch (error) {\n console.error('Error loading sync config:', error);\n return null;\n }\n }\n \n return null;\n}\n\n/**\n * Load entity-specific configuration from a directory\n * \n * Loads .mj-sync.json file that contains an \"entity\" field, indicating this\n * directory contains data for a specific entity type. Returns null if the\n * file doesn't exist or doesn't contain an entity field.\n * \n * @param dir - Directory path to load configuration from\n * @returns Promise resolving to EntityConfig if found and valid, null otherwise\n * \n * @example\n * ```typescript\n * const entityConfig = await loadEntityConfig('./ai-prompts');\n * if (entityConfig) {\n * console.log(`Directory contains ${entityConfig.entity} records`);\n * }\n * ```\n */\nexport async function loadEntityConfig(dir: string): Promise<EntityConfig | null> {\n const configPath = path.join(dir, '.mj-sync.json');\n \n if (await fs.pathExists(configPath)) {\n try {\n const config = await fs.readJson(configPath);\n if (config.entity) {\n return config;\n }\n } catch (error) {\n console.error('Error loading entity config:', error);\n }\n }\n \n return null;\n}\n\n/**\n * Load folder-level configuration\n * \n * Loads .mj-folder.json file that contains default values to be applied to\n * all entities in this folder and its subfolders. Used for cascading defaults\n * in deep directory structures.\n * \n * @param dir - Directory path to load configuration from\n * @returns Promise resolving to FolderConfig if found and valid, null otherwise\n * \n * @example\n * ```typescript\n * const folderConfig = await loadFolderConfig('./templates');\n * if (folderConfig?.defaults) {\n * // Apply folder defaults to records\n * }\n * ```\n */\nexport async function loadFolderConfig(dir: string): Promise<FolderConfig | null> {\n const configPath = path.join(dir, '.mj-folder.json');\n \n if (await fs.pathExists(configPath)) {\n try {\n return await fs.readJson(configPath);\n } catch (error) {\n console.error('Error loading folder config:', error);\n return null;\n }\n }\n \n return null;\n}"]}
@@ -28,8 +28,13 @@ const dotenv = __importStar(require("dotenv"));
28
28
  const path = __importStar(require("path"));
29
29
  const provider_utils_1 = require("../lib/provider-utils");
30
30
  const hook = async function () {
31
- // Load .env from the repository root
31
+ // Load .env from the repository root first
32
32
  dotenv.config({ path: path.join(__dirname, '../../../../.env') });
33
+ // Then load local .env from package directory to override
34
+ dotenv.config({
35
+ path: path.join(__dirname, '../../.env'),
36
+ override: true // This ensures local values override already loaded ones
37
+ });
33
38
  // Load core entities server subclasses
34
39
  (0, core_entities_server_1.LoadCoreEntitiesServerSubClasses)();
35
40
  // Register cleanup handlers
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/hooks/init.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AACA,+EAAwF;AACxF,+CAAiC;AACjC,2CAA6B;AAC7B,0DAAwD;AAExD,MAAM,IAAI,GAAiB,KAAK;IAC9B,qCAAqC;IACrC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAElE,uCAAuC;IACvC,IAAA,uDAAgC,GAAE,CAAC;IAEnC,4BAA4B;IAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACtB,IAAA,gCAAe,GAAE,CAAC,KAAK,CAAC,GAAG,EAAE;YAC3B,+BAA+B;QACjC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,IAAA,gCAAe,GAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,IAAA,gCAAe,GAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,kBAAe,IAAI,CAAC","sourcesContent":["import { Hook } from '@oclif/core';\nimport { LoadCoreEntitiesServerSubClasses } from '@memberjunction/core-entities-server';\nimport * as dotenv from 'dotenv';\nimport * as path from 'path';\nimport { cleanupProvider } from '../lib/provider-utils';\n\nconst hook: Hook<'init'> = async function () {\n // Load .env from the repository root\n dotenv.config({ path: path.join(__dirname, '../../../../.env') });\n \n // Load core entities server subclasses\n LoadCoreEntitiesServerSubClasses();\n \n // Register cleanup handlers\n process.on('exit', () => {\n cleanupProvider().catch(() => {\n // Ignore errors during cleanup\n });\n });\n \n process.on('SIGINT', async () => {\n await cleanupProvider();\n process.exit(0);\n });\n \n process.on('SIGTERM', async () => {\n await cleanupProvider();\n process.exit(0);\n });\n};\n\nexport default hook;"]}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/hooks/init.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AACA,+EAAwF;AACxF,+CAAiC;AACjC,2CAA6B;AAC7B,0DAAwD;AAExD,MAAM,IAAI,GAAiB,KAAK;IAC9B,2CAA2C;IAC3C,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAElE,0DAA0D;IAC1D,MAAM,CAAC,MAAM,CAAC;QACZ,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC;QACxC,QAAQ,EAAE,IAAI,CAAE,yDAAyD;KAC1E,CAAC,CAAC;IAEH,uCAAuC;IACvC,IAAA,uDAAgC,GAAE,CAAC;IAEnC,4BAA4B;IAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACtB,IAAA,gCAAe,GAAE,CAAC,KAAK,CAAC,GAAG,EAAE;YAC3B,+BAA+B;QACjC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,IAAA,gCAAe,GAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,IAAA,gCAAe,GAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,kBAAe,IAAI,CAAC","sourcesContent":["import { Hook } from '@oclif/core';\nimport { LoadCoreEntitiesServerSubClasses } from '@memberjunction/core-entities-server';\nimport * as dotenv from 'dotenv';\nimport * as path from 'path';\nimport { cleanupProvider } from '../lib/provider-utils';\n\nconst hook: Hook<'init'> = async function () {\n // Load .env from the repository root first\n dotenv.config({ path: path.join(__dirname, '../../../../.env') });\n \n // Then load local .env from package directory to override\n dotenv.config({ \n path: path.join(__dirname, '../../.env'),\n override: true // This ensures local values override already loaded ones\n });\n \n // Load core entities server subclasses\n LoadCoreEntitiesServerSubClasses();\n \n // Register cleanup handlers\n process.on('exit', () => {\n cleanupProvider().catch(() => {\n // Ignore errors during cleanup\n });\n });\n \n process.on('SIGINT', async () => {\n await cleanupProvider();\n process.exit(0);\n });\n \n process.on('SIGTERM', async () => {\n await cleanupProvider();\n process.exit(0);\n });\n};\n\nexport default hook;"]}
@@ -1,13 +1,85 @@
1
+ /**
2
+ * @fileoverview Database provider utilities for MetadataSync
3
+ * @module provider-utils
4
+ *
5
+ * This module provides utilities for initializing and managing the database
6
+ * connection, accessing system users, and finding entity directories. It handles
7
+ * the TypeORM DataSource lifecycle and MemberJunction provider initialization.
8
+ */
1
9
  import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
2
10
  import type { MJConfig } from '../config';
3
11
  import { UserInfo } from '@memberjunction/core';
12
+ /**
13
+ * Initialize a SQLServerDataProvider with the given configuration
14
+ *
15
+ * Creates and initializes a TypeORM DataSource for SQL Server, then sets up
16
+ * the MemberJunction SQLServerDataProvider. The connection is stored globally
17
+ * for proper cleanup. Auto-detects Azure SQL databases for encryption settings.
18
+ *
19
+ * @param config - MemberJunction configuration with database connection details
20
+ * @returns Promise resolving to initialized SQLServerDataProvider instance
21
+ * @throws Error if database connection fails
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const config = loadMJConfig();
26
+ * const provider = await initializeProvider(config);
27
+ * // Provider is ready for use
28
+ * ```
29
+ */
4
30
  export declare function initializeProvider(config: MJConfig): Promise<SQLServerDataProvider>;
31
+ /**
32
+ * Clean up the global database connection
33
+ *
34
+ * Destroys the TypeORM DataSource if it exists and is initialized.
35
+ * Should be called when the CLI command completes to ensure proper cleanup.
36
+ *
37
+ * @returns Promise that resolves when cleanup is complete
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * try {
42
+ * // Do work with database
43
+ * } finally {
44
+ * await cleanupProvider();
45
+ * }
46
+ * ```
47
+ */
5
48
  export declare function cleanupProvider(): Promise<void>;
49
+ /**
50
+ * Get the system user from the UserCache
51
+ *
52
+ * Retrieves the "System" user from MemberJunction's UserCache. This user is
53
+ * typically used for CLI operations where no specific user context exists.
54
+ *
55
+ * @returns The System UserInfo object
56
+ * @throws Error if System user is not found in the cache
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const systemUser = getSystemUser();
61
+ * const syncEngine = new SyncEngine(systemUser);
62
+ * ```
63
+ */
6
64
  export declare function getSystemUser(): UserInfo;
7
65
  /**
8
- * Recursively find all entity directories with .mj-sync.json files
9
- * @param dir Directory to search
10
- * @param specificDir Optional specific directory to limit search to
11
- * @returns Array of directory paths containing .mj-sync.json files
66
+ * Find entity directories at the immediate level only
67
+ *
68
+ * Searches for directories containing .mj-sync.json files, which indicate
69
+ * entity data directories. Only searches immediate subdirectories, not recursive.
70
+ * If a specific directory is provided, only checks that directory.
71
+ *
72
+ * @param dir - Base directory to search from
73
+ * @param specificDir - Optional specific subdirectory name to check
74
+ * @returns Array of absolute directory paths containing .mj-sync.json files
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Find all entity directories
79
+ * const dirs = findEntityDirectories(process.cwd());
80
+ *
81
+ * // Check specific directory
82
+ * const dirs = findEntityDirectories(process.cwd(), 'ai-prompts');
83
+ * ```
12
84
  */
13
85
  export declare function findEntityDirectories(dir: string, specificDir?: string): string[];