@memberjunction/metadata-sync 2.46.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.
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@oclif/core");
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chokidar_1 = __importDefault(require("chokidar"));
10
+ const ora_classic_1 = __importDefault(require("ora-classic"));
11
+ const config_1 = require("../../config");
12
+ const sync_engine_1 = require("../../lib/sync-engine");
13
+ const provider_utils_1 = require("../../lib/provider-utils");
14
+ class Watch extends core_1.Command {
15
+ static description = 'Watch for file changes and automatically push to database';
16
+ static examples = [
17
+ `<%= config.bin %> <%= command.id %>`,
18
+ `<%= config.bin %> <%= command.id %> --dir="ai-prompts"`,
19
+ ];
20
+ static flags = {
21
+ dir: core_1.Flags.string({ description: 'Specific entity directory to watch' }),
22
+ };
23
+ syncEngine;
24
+ syncConfig;
25
+ debounceTimers = new Map();
26
+ async run() {
27
+ const { flags } = await this.parse(Watch);
28
+ const spinner = (0, ora_classic_1.default)();
29
+ try {
30
+ // Load configurations
31
+ spinner.start('Loading configuration');
32
+ const mjConfig = (0, config_1.loadMJConfig)();
33
+ if (!mjConfig) {
34
+ this.error('No mj.config.cjs found in current directory or parent directories');
35
+ }
36
+ this.syncConfig = await (0, config_1.loadSyncConfig)(process.cwd());
37
+ // Initialize data provider
38
+ const provider = await (0, provider_utils_1.initializeProvider)(mjConfig);
39
+ // Initialize sync engine
40
+ this.syncEngine = new sync_engine_1.SyncEngine((0, provider_utils_1.getSystemUser)());
41
+ await this.syncEngine.initialize();
42
+ spinner.succeed('Configuration loaded');
43
+ // Find entity directories to watch
44
+ const entityDirs = (0, provider_utils_1.findEntityDirectories)(process.cwd(), flags.dir);
45
+ if (entityDirs.length === 0) {
46
+ this.error('No entity directories found');
47
+ }
48
+ this.log(`Watching ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} for changes`);
49
+ // Set up watchers
50
+ const watchers = [];
51
+ for (const entityDir of entityDirs) {
52
+ const entityConfig = await (0, config_1.loadEntityConfig)(entityDir);
53
+ if (!entityConfig) {
54
+ this.warn(`Skipping ${entityDir} - no valid entity configuration`);
55
+ continue;
56
+ }
57
+ this.log(`Watching ${entityConfig.entity} in ${entityDir}`);
58
+ // Watch for JSON files and external files
59
+ const patterns = [
60
+ path_1.default.join(entityDir, entityConfig.filePattern || '*.json'),
61
+ path_1.default.join(entityDir, '**/*.md'),
62
+ path_1.default.join(entityDir, '**/*.txt'),
63
+ path_1.default.join(entityDir, '**/*.html'),
64
+ path_1.default.join(entityDir, '**/*.liquid'),
65
+ path_1.default.join(entityDir, '**/*.sql')
66
+ ];
67
+ const ignored = [
68
+ '**/node_modules/**',
69
+ '**/.git/**',
70
+ '**/.mj-sync.json',
71
+ '**/.mj-folder.json',
72
+ ...(this.syncConfig?.watch?.ignorePatterns || [])
73
+ ];
74
+ const watcher = chokidar_1.default.watch(patterns, {
75
+ ignored,
76
+ persistent: true,
77
+ ignoreInitial: true
78
+ });
79
+ watcher
80
+ .on('add', (filePath) => this.handleFileChange(filePath, 'added', entityDir, entityConfig))
81
+ .on('change', (filePath) => this.handleFileChange(filePath, 'changed', entityDir, entityConfig))
82
+ .on('unlink', (filePath) => this.handleFileChange(filePath, 'deleted', entityDir, entityConfig));
83
+ watchers.push(watcher);
84
+ }
85
+ this.log('\nPress Ctrl+C to stop watching');
86
+ // Keep process alive
87
+ process.stdin.resume();
88
+ // Cleanup on exit
89
+ process.on('SIGINT', () => {
90
+ this.log('\nStopping watchers...');
91
+ watchers.forEach(w => w.close());
92
+ process.exit(0);
93
+ });
94
+ }
95
+ catch (error) {
96
+ spinner.fail('Watch failed');
97
+ this.error(error);
98
+ }
99
+ }
100
+ async handleFileChange(filePath, event, entityDir, entityConfig) {
101
+ // Clear existing debounce timer
102
+ const existingTimer = this.debounceTimers.get(filePath);
103
+ if (existingTimer) {
104
+ clearTimeout(existingTimer);
105
+ }
106
+ // Set new debounce timer
107
+ const debounceMs = this.syncConfig?.watch?.debounceMs || 1000;
108
+ const timer = setTimeout(async () => {
109
+ this.debounceTimers.delete(filePath);
110
+ try {
111
+ const relativePath = path_1.default.relative(entityDir, filePath);
112
+ this.log(`\nFile ${event}: ${relativePath}`);
113
+ if (event === 'deleted') {
114
+ // Handle deletion
115
+ this.log('File deletion detected - manual database cleanup may be required');
116
+ }
117
+ else if (filePath.endsWith('.json')) {
118
+ // Handle JSON file change
119
+ await this.syncJsonFile(filePath, entityDir, entityConfig);
120
+ }
121
+ else {
122
+ // Handle external file change
123
+ await this.syncExternalFile(filePath, entityDir, entityConfig);
124
+ }
125
+ }
126
+ catch (error) {
127
+ this.warn(`Failed to sync ${filePath}: ${error.message || error}`);
128
+ }
129
+ }, debounceMs);
130
+ this.debounceTimers.set(filePath, timer);
131
+ }
132
+ async syncJsonFile(filePath, entityDir, entityConfig) {
133
+ const recordData = await fs_extra_1.default.readJson(filePath);
134
+ // Build defaults
135
+ const defaults = await this.syncEngine.buildDefaults(filePath, entityConfig);
136
+ // Load or create entity
137
+ let entity = null;
138
+ let isNew = false;
139
+ if (recordData.primaryKey) {
140
+ entity = await this.syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);
141
+ }
142
+ if (!entity) {
143
+ // New record
144
+ entity = await this.syncEngine.createEntityObject(entityConfig.entity);
145
+ entity.NewRecord();
146
+ isNew = true;
147
+ }
148
+ // Apply defaults first
149
+ for (const [field, value] of Object.entries(defaults)) {
150
+ if (field in entity) {
151
+ entity[field] = value;
152
+ }
153
+ }
154
+ // Apply record fields
155
+ for (const [field, value] of Object.entries(recordData.fields)) {
156
+ if (field in entity) {
157
+ const processedValue = await this.syncEngine.processFieldValue(value, path_1.default.dirname(filePath));
158
+ entity[field] = processedValue;
159
+ }
160
+ }
161
+ // Save the record
162
+ const saved = await entity.Save();
163
+ if (!saved) {
164
+ const errors = entity.LatestResult?.Errors?.join(', ') || 'Unknown error';
165
+ throw new Error(`Failed to save record: ${errors}`);
166
+ }
167
+ this.log(`Successfully ${isNew ? 'created' : 'updated'} ${entityConfig.entity} record`);
168
+ // Update the local file with new primary key if created
169
+ if (isNew) {
170
+ const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);
171
+ if (entityInfo) {
172
+ const newPrimaryKey = {};
173
+ for (const pk of entityInfo.PrimaryKeys) {
174
+ newPrimaryKey[pk.Name] = entity.Get(pk.Name);
175
+ }
176
+ recordData.primaryKey = newPrimaryKey;
177
+ // Update sync metadata
178
+ recordData.sync = {
179
+ lastModified: new Date().toISOString(),
180
+ checksum: this.syncEngine.calculateChecksum(recordData.fields)
181
+ };
182
+ // Write back to file
183
+ await fs_extra_1.default.writeJson(filePath, recordData, { spaces: 2 });
184
+ }
185
+ }
186
+ }
187
+ async syncExternalFile(filePath, entityDir, entityConfig) {
188
+ // Find the corresponding JSON file
189
+ const fileName = path_1.default.basename(filePath);
190
+ const parts = fileName.split('.');
191
+ if (parts.length >= 3) {
192
+ // Format: uuid.fieldname.ext
193
+ const jsonFileName = `${parts[0]}.json`;
194
+ const fieldName = parts[1];
195
+ const jsonFilePath = path_1.default.join(path_1.default.dirname(filePath), jsonFileName);
196
+ if (await fs_extra_1.default.pathExists(jsonFilePath)) {
197
+ // Update the JSON file's sync metadata to trigger a sync
198
+ const recordData = await fs_extra_1.default.readJson(jsonFilePath);
199
+ recordData.sync = {
200
+ lastModified: new Date().toISOString(),
201
+ checksum: recordData.sync?.checksum || ''
202
+ };
203
+ await fs_extra_1.default.writeJson(jsonFilePath, recordData, { spaces: 2 });
204
+ this.log(`Updated sync metadata for ${jsonFileName} due to external file change`);
205
+ }
206
+ }
207
+ }
208
+ }
209
+ exports.default = Watch;
210
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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}"]}
@@ -0,0 +1,44 @@
1
+ export interface MJConfig {
2
+ dbHost: string;
3
+ dbPort?: number;
4
+ dbDatabase: string;
5
+ dbUsername: string;
6
+ dbPassword: string;
7
+ dbTrustServerCertificate?: string;
8
+ dbInstanceName?: string;
9
+ mjCoreSchema?: string;
10
+ [key: string]: any;
11
+ }
12
+ export interface SyncConfig {
13
+ version: string;
14
+ push?: {
15
+ validateBeforePush?: boolean;
16
+ requireConfirmation?: boolean;
17
+ };
18
+ watch?: {
19
+ debounceMs?: number;
20
+ ignorePatterns?: string[];
21
+ };
22
+ }
23
+ export interface RelatedEntityConfig {
24
+ entity: string;
25
+ foreignKey: string;
26
+ filter?: string;
27
+ relatedEntities?: Record<string, RelatedEntityConfig>;
28
+ }
29
+ export interface EntityConfig {
30
+ entity: string;
31
+ filePattern?: string;
32
+ defaults?: Record<string, any>;
33
+ pull?: {
34
+ filter?: string;
35
+ relatedEntities?: Record<string, RelatedEntityConfig>;
36
+ };
37
+ }
38
+ export interface FolderConfig {
39
+ defaults: Record<string, any>;
40
+ }
41
+ export declare function loadMJConfig(): MJConfig | null;
42
+ export declare function loadSyncConfig(dir: string): Promise<SyncConfig | null>;
43
+ export declare function loadEntityConfig(dir: string): Promise<EntityConfig | null>;
44
+ export declare function loadFolderConfig(dir: string): Promise<FolderConfig | null>;
package/dist/config.js ADDED
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadFolderConfig = exports.loadEntityConfig = exports.loadSyncConfig = exports.loadMJConfig = void 0;
7
+ const cosmiconfig_1 = require("cosmiconfig");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ function loadMJConfig() {
11
+ try {
12
+ const explorer = (0, cosmiconfig_1.cosmiconfigSync)('mj');
13
+ const result = explorer.search(process.cwd());
14
+ if (!result || !result.config) {
15
+ throw new Error('No mj.config.cjs found');
16
+ }
17
+ return result.config;
18
+ }
19
+ catch (error) {
20
+ console.error('Error loading MJ config:', error);
21
+ return null;
22
+ }
23
+ }
24
+ exports.loadMJConfig = loadMJConfig;
25
+ async function loadSyncConfig(dir) {
26
+ const configPath = path_1.default.join(dir, '.mj-sync.json');
27
+ if (await fs_extra_1.default.pathExists(configPath)) {
28
+ try {
29
+ return await fs_extra_1.default.readJson(configPath);
30
+ }
31
+ catch (error) {
32
+ console.error('Error loading sync config:', error);
33
+ return null;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ exports.loadSyncConfig = loadSyncConfig;
39
+ async function loadEntityConfig(dir) {
40
+ const configPath = path_1.default.join(dir, '.mj-sync.json');
41
+ if (await fs_extra_1.default.pathExists(configPath)) {
42
+ try {
43
+ const config = await fs_extra_1.default.readJson(configPath);
44
+ if (config.entity) {
45
+ return config;
46
+ }
47
+ }
48
+ catch (error) {
49
+ console.error('Error loading entity config:', error);
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ exports.loadEntityConfig = loadEntityConfig;
55
+ async function loadFolderConfig(dir) {
56
+ const configPath = path_1.default.join(dir, '.mj-folder.json');
57
+ if (await fs_extra_1.default.pathExists(configPath)) {
58
+ try {
59
+ return await fs_extra_1.default.readJson(configPath);
60
+ }
61
+ catch (error) {
62
+ console.error('Error loading folder config:', error);
63
+ return null;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ exports.loadFolderConfig = loadFolderConfig;
69
+ //# sourceMappingURL=config.js.map
@@ -0,0 +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}"]}
@@ -0,0 +1,3 @@
1
+ import { Hook } from '@oclif/core';
2
+ declare const hook: Hook<'init'>;
3
+ export default hook;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ const core_entities_server_1 = require("@memberjunction/core-entities-server");
27
+ const dotenv = __importStar(require("dotenv"));
28
+ const path = __importStar(require("path"));
29
+ const provider_utils_1 = require("../lib/provider-utils");
30
+ const hook = async function () {
31
+ // Load .env from the repository root
32
+ dotenv.config({ path: path.join(__dirname, '../../../../.env') });
33
+ // Load core entities server subclasses
34
+ (0, core_entities_server_1.LoadCoreEntitiesServerSubClasses)();
35
+ // Register cleanup handlers
36
+ process.on('exit', () => {
37
+ (0, provider_utils_1.cleanupProvider)().catch(() => {
38
+ // Ignore errors during cleanup
39
+ });
40
+ });
41
+ process.on('SIGINT', async () => {
42
+ await (0, provider_utils_1.cleanupProvider)();
43
+ process.exit(0);
44
+ });
45
+ process.on('SIGTERM', async () => {
46
+ await (0, provider_utils_1.cleanupProvider)();
47
+ process.exit(0);
48
+ });
49
+ };
50
+ exports.default = hook;
51
+ //# sourceMappingURL=init.js.map
@@ -0,0 +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;"]}
@@ -0,0 +1 @@
1
+ export { run } from '@oclif/core';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run = void 0;
4
+ var core_1 = require("@oclif/core");
5
+ Object.defineProperty(exports, "run", { enumerable: true, get: function () { return core_1.run; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,oCAAkC;AAAzB,2FAAA,GAAG,OAAA","sourcesContent":["export { run } from '@oclif/core';"]}
@@ -0,0 +1,13 @@
1
+ import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
2
+ import type { MJConfig } from '../config';
3
+ import { UserInfo } from '@memberjunction/core';
4
+ export declare function initializeProvider(config: MJConfig): Promise<SQLServerDataProvider>;
5
+ export declare function cleanupProvider(): Promise<void>;
6
+ export declare function getSystemUser(): UserInfo;
7
+ /**
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
12
+ */
13
+ export declare function findEntityDirectories(dir: string, specificDir?: string): string[];
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.findEntityDirectories = exports.getSystemUser = exports.cleanupProvider = exports.initializeProvider = void 0;
27
+ const typeorm_1 = require("typeorm");
28
+ const sqlserver_dataprovider_1 = require("@memberjunction/sqlserver-dataprovider");
29
+ const fs = __importStar(require("fs"));
30
+ const path = __importStar(require("path"));
31
+ /**
32
+ * Initialize a SQLServerDataProvider with the given configuration
33
+ * @param config MemberJunction configuration with database connection details
34
+ * @returns Initialized SQLServerDataProvider instance
35
+ */
36
+ let globalDataSource = null;
37
+ async function initializeProvider(config) {
38
+ // Create TypeORM DataSource
39
+ const dataSource = new typeorm_1.DataSource({
40
+ type: 'mssql',
41
+ host: config.dbHost,
42
+ port: config.dbPort ? Number(config.dbPort) : 1433,
43
+ database: config.dbDatabase,
44
+ username: config.dbUsername,
45
+ password: config.dbPassword,
46
+ synchronize: false,
47
+ logging: false,
48
+ options: {
49
+ encrypt: config.dbTrustServerCertificate !== 'Y' ? false : true,
50
+ trustServerCertificate: config.dbTrustServerCertificate === 'Y',
51
+ instanceName: config.dbInstanceName
52
+ }
53
+ });
54
+ // Initialize the data source
55
+ await dataSource.initialize();
56
+ // Store for cleanup
57
+ globalDataSource = dataSource;
58
+ // Create provider config
59
+ const providerConfig = new sqlserver_dataprovider_1.SQLServerProviderConfigData(dataSource, 'system@sync.cli', // Default user for CLI
60
+ config.mjCoreSchema || '__mj', 0);
61
+ // Use setupSQLServerClient to properly initialize
62
+ return await (0, sqlserver_dataprovider_1.setupSQLServerClient)(providerConfig);
63
+ }
64
+ exports.initializeProvider = initializeProvider;
65
+ async function cleanupProvider() {
66
+ if (globalDataSource && globalDataSource.isInitialized) {
67
+ await globalDataSource.destroy();
68
+ globalDataSource = null;
69
+ }
70
+ }
71
+ exports.cleanupProvider = cleanupProvider;
72
+ function getSystemUser() {
73
+ const sysUser = sqlserver_dataprovider_1.UserCache.Instance.UserByName("System", false);
74
+ if (!sysUser) {
75
+ throw new Error("System user not found in cache. Ensure the system user exists in the database.");
76
+ }
77
+ return sysUser;
78
+ }
79
+ exports.getSystemUser = getSystemUser;
80
+ /**
81
+ * Recursively find all entity directories with .mj-sync.json files
82
+ * @param dir Directory to search
83
+ * @param specificDir Optional specific directory to limit search to
84
+ * @returns Array of directory paths containing .mj-sync.json files
85
+ */
86
+ function findEntityDirectories(dir, specificDir) {
87
+ const results = [];
88
+ function search(currentDir) {
89
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
90
+ // Check if this directory has .mj-sync.json (and it's not the root)
91
+ const hasSyncConfig = entries.some(e => e.name === '.mj-sync.json');
92
+ const isRoot = currentDir === dir;
93
+ if (hasSyncConfig && !isRoot) {
94
+ results.push(currentDir);
95
+ }
96
+ // Recursively search subdirectories
97
+ for (const entry of entries) {
98
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
99
+ search(path.join(currentDir, entry.name));
100
+ }
101
+ }
102
+ }
103
+ // If specific directory is provided, only search within it
104
+ if (specificDir) {
105
+ // Handle both absolute and relative paths
106
+ const targetDir = path.isAbsolute(specificDir) ? specificDir : path.join(dir, specificDir);
107
+ if (fs.existsSync(targetDir)) {
108
+ search(targetDir);
109
+ }
110
+ }
111
+ else {
112
+ search(dir);
113
+ }
114
+ return results;
115
+ }
116
+ exports.findEntityDirectories = findEntityDirectories;
117
+ //# sourceMappingURL=provider-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-utils.js","sourceRoot":"","sources":["../../src/lib/provider-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { DataSource } from 'typeorm';\nimport { SQLServerDataProvider, SQLServerProviderConfigData, UserCache, setupSQLServerClient } from '@memberjunction/sqlserver-dataprovider';\nimport type { MJConfig } from '../config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { UserInfo } from '@memberjunction/core';\n\n/**\n * Initialize a SQLServerDataProvider with the given configuration\n * @param config MemberJunction configuration with database connection details\n * @returns Initialized SQLServerDataProvider instance\n */\nlet globalDataSource: DataSource | null = null;\n\nexport async function initializeProvider(config: MJConfig): Promise<SQLServerDataProvider> {\n // Create TypeORM DataSource\n const dataSource = new DataSource({\n type: 'mssql',\n host: config.dbHost,\n port: config.dbPort ? Number(config.dbPort) : 1433,\n database: config.dbDatabase,\n username: config.dbUsername,\n password: config.dbPassword,\n synchronize: false,\n logging: false,\n options: {\n encrypt: config.dbTrustServerCertificate !== 'Y' ? false : true,\n trustServerCertificate: config.dbTrustServerCertificate === 'Y',\n instanceName: config.dbInstanceName\n }\n });\n \n // Initialize the data source\n await dataSource.initialize();\n \n // Store for cleanup\n globalDataSource = dataSource;\n \n // Create provider config\n const providerConfig = new SQLServerProviderConfigData(\n dataSource,\n 'system@sync.cli', // Default user for CLI\n config.mjCoreSchema || '__mj',\n 0\n );\n \n // Use setupSQLServerClient to properly initialize\n return await setupSQLServerClient(providerConfig);\n}\n\nexport async function cleanupProvider(): Promise<void> {\n if (globalDataSource && globalDataSource.isInitialized) {\n await globalDataSource.destroy();\n globalDataSource = null;\n }\n}\n\nexport function getSystemUser(): UserInfo {\n const sysUser = UserCache.Instance.UserByName(\"System\", false);\n if (!sysUser) {\n throw new Error(\"System user not found in cache. Ensure the system user exists in the database.\"); \n }\n return sysUser;\n}\n\n/**\n * Recursively find all entity directories with .mj-sync.json files\n * @param dir Directory to search\n * @param specificDir Optional specific directory to limit search to\n * @returns Array of directory paths containing .mj-sync.json files\n */\nexport function findEntityDirectories(dir: string, specificDir?: string): string[] {\n const results: string[] = [];\n \n function search(currentDir: string) {\n const entries = fs.readdirSync(currentDir, { withFileTypes: true });\n \n // Check if this directory has .mj-sync.json (and it's not the root)\n const hasSyncConfig = entries.some(e => e.name === '.mj-sync.json');\n const isRoot = currentDir === dir;\n \n if (hasSyncConfig && !isRoot) {\n results.push(currentDir);\n }\n \n // Recursively search subdirectories\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith('.')) {\n search(path.join(currentDir, entry.name));\n }\n }\n }\n \n // If specific directory is provided, only search within it\n if (specificDir) {\n // Handle both absolute and relative paths\n const targetDir = path.isAbsolute(specificDir) ? specificDir : path.join(dir, specificDir);\n if (fs.existsSync(targetDir)) {\n search(targetDir);\n }\n } else {\n search(dir);\n }\n \n return results;\n}"]}
@@ -0,0 +1,49 @@
1
+ import { EntityInfo, BaseEntity, UserInfo } from '@memberjunction/core';
2
+ import { EntityConfig } from '../config';
3
+ export interface RecordData {
4
+ primaryKey?: Record<string, any>;
5
+ fields: Record<string, any>;
6
+ relatedEntities?: Record<string, RecordData[]>;
7
+ sync?: {
8
+ lastModified: string;
9
+ checksum: string;
10
+ };
11
+ }
12
+ export declare class SyncEngine {
13
+ private metadata;
14
+ private contextUser;
15
+ constructor(contextUser: UserInfo);
16
+ initialize(): Promise<void>;
17
+ /**
18
+ * Process special references in field values
19
+ */
20
+ processFieldValue(value: any, baseDir: string, parentRecord?: BaseEntity | null, rootRecord?: BaseEntity | null): Promise<any>;
21
+ /**
22
+ * Resolve a lookup reference to an ID, optionally creating the record if it doesn't exist
23
+ */
24
+ resolveLookup(entityName: string, fieldName: string, fieldValue: string, autoCreate?: boolean, createFields?: Record<string, any>): Promise<string>;
25
+ /**
26
+ * Build cascading defaults for a file path and process field values
27
+ */
28
+ buildDefaults(filePath: string, entityConfig: EntityConfig): Promise<Record<string, any>>;
29
+ /**
30
+ * Load folder configuration
31
+ */
32
+ private loadFolderConfig;
33
+ /**
34
+ * Calculate checksum for data
35
+ */
36
+ calculateChecksum(data: any): string;
37
+ /**
38
+ * Get entity info by name
39
+ */
40
+ getEntityInfo(entityName: string): EntityInfo | null;
41
+ /**
42
+ * Create a new entity object
43
+ */
44
+ createEntityObject(entityName: string): Promise<BaseEntity>;
45
+ /**
46
+ * Load an entity by primary key
47
+ */
48
+ loadEntity(entityName: string, primaryKey: Record<string, any>): Promise<BaseEntity | null>;
49
+ }