@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,286 @@
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 prompts_1 = require("@inquirer/prompts");
10
+ const ora_classic_1 = __importDefault(require("ora-classic"));
11
+ const fast_glob_1 = __importDefault(require("fast-glob"));
12
+ const config_1 = require("../../config");
13
+ const sync_engine_1 = require("../../lib/sync-engine");
14
+ const provider_utils_1 = require("../../lib/provider-utils");
15
+ const provider_utils_2 = require("../../lib/provider-utils");
16
+ class Push extends core_1.Command {
17
+ static description = 'Push local file changes to the database';
18
+ static examples = [
19
+ `<%= config.bin %> <%= command.id %>`,
20
+ `<%= config.bin %> <%= command.id %> --dry-run`,
21
+ `<%= config.bin %> <%= command.id %> --dir="ai-prompts"`,
22
+ `<%= config.bin %> <%= command.id %> --ci`,
23
+ ];
24
+ static flags = {
25
+ dir: core_1.Flags.string({ description: 'Specific entity directory to push' }),
26
+ 'dry-run': core_1.Flags.boolean({ description: 'Show what would be pushed without actually pushing' }),
27
+ ci: core_1.Flags.boolean({ description: 'CI mode - no prompts, fail on issues' }),
28
+ verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed field-level output' }),
29
+ };
30
+ async run() {
31
+ const { flags } = await this.parse(Push);
32
+ const spinner = (0, ora_classic_1.default)();
33
+ try {
34
+ // Load configurations
35
+ spinner.start('Loading configuration');
36
+ const mjConfig = (0, config_1.loadMJConfig)();
37
+ if (!mjConfig) {
38
+ this.error('No mj.config.cjs found in current directory or parent directories');
39
+ }
40
+ const syncConfig = await (0, config_1.loadSyncConfig)(process.cwd());
41
+ // Initialize data provider
42
+ await (0, provider_utils_1.initializeProvider)(mjConfig);
43
+ // Initialize sync engine
44
+ const syncEngine = new sync_engine_1.SyncEngine((0, provider_utils_1.getSystemUser)());
45
+ await syncEngine.initialize();
46
+ spinner.succeed('Configuration loaded');
47
+ // Find entity directories to process
48
+ const entityDirs = (0, provider_utils_1.findEntityDirectories)(process.cwd(), flags.dir);
49
+ if (entityDirs.length === 0) {
50
+ this.error('No entity directories found');
51
+ }
52
+ this.log(`Found ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} to process`);
53
+ // Process each entity directory
54
+ let totalCreated = 0;
55
+ let totalUpdated = 0;
56
+ let totalErrors = 0;
57
+ for (const entityDir of entityDirs) {
58
+ const entityConfig = await (0, config_1.loadEntityConfig)(entityDir);
59
+ if (!entityConfig) {
60
+ this.warn(`Skipping ${entityDir} - no valid entity configuration`);
61
+ continue;
62
+ }
63
+ this.log(`\nProcessing ${entityConfig.entity} in ${entityDir}`);
64
+ const result = await this.processEntityDirectory(entityDir, entityConfig, syncEngine, flags, syncConfig);
65
+ totalCreated += result.created;
66
+ totalUpdated += result.updated;
67
+ totalErrors += result.errors;
68
+ }
69
+ // Summary
70
+ this.log('\n=== Push Summary ===');
71
+ this.log(`Created: ${totalCreated}`);
72
+ this.log(`Updated: ${totalUpdated}`);
73
+ this.log(`Errors: ${totalErrors}`);
74
+ if (totalErrors > 0 && flags.ci) {
75
+ this.error('Push failed with errors in CI mode');
76
+ }
77
+ }
78
+ catch (error) {
79
+ spinner.fail('Push failed');
80
+ this.error(error);
81
+ }
82
+ finally {
83
+ // Clean up database connection
84
+ await (0, provider_utils_2.cleanupProvider)();
85
+ }
86
+ }
87
+ async processEntityDirectory(entityDir, entityConfig, syncEngine, flags, syncConfig) {
88
+ const result = { created: 0, updated: 0, errors: 0 };
89
+ // Find all JSON files
90
+ const pattern = entityConfig.filePattern || '*.json';
91
+ const jsonFiles = await (0, fast_glob_1.default)(pattern, {
92
+ cwd: entityDir,
93
+ ignore: ['.mj-sync.json', '.mj-folder.json']
94
+ });
95
+ this.log(`Found ${jsonFiles.length} records to process`);
96
+ if (jsonFiles.length === 0) {
97
+ return result;
98
+ }
99
+ // Confirm if needed
100
+ if (!flags['dry-run'] && !flags.ci && syncConfig?.push?.requireConfirmation) {
101
+ const proceed = await (0, prompts_1.confirm)({
102
+ message: `Push ${jsonFiles.length} ${entityConfig.entity} records to database?`
103
+ });
104
+ if (!proceed) {
105
+ this.log('Push cancelled');
106
+ return result;
107
+ }
108
+ }
109
+ // Process each file
110
+ const spinner = (0, ora_classic_1.default)();
111
+ spinner.start('Processing records');
112
+ for (const file of jsonFiles) {
113
+ try {
114
+ const filePath = path_1.default.join(entityDir, file);
115
+ const recordData = await fs_extra_1.default.readJson(filePath);
116
+ // Build and process defaults (including lookups)
117
+ const defaults = await syncEngine.buildDefaults(filePath, entityConfig);
118
+ // Process the record
119
+ const isNew = await this.pushRecord(recordData, entityConfig.entity, path_1.default.dirname(filePath), file, defaults, syncEngine, flags['dry-run'], flags.verbose);
120
+ if (!flags['dry-run']) {
121
+ if (isNew) {
122
+ result.created++;
123
+ }
124
+ else {
125
+ result.updated++;
126
+ }
127
+ }
128
+ spinner.text = `Processing records (${result.created + result.updated + result.errors}/${jsonFiles.length})`;
129
+ }
130
+ catch (error) {
131
+ result.errors++;
132
+ const errorMessage = error instanceof Error ? error.message : String(error);
133
+ this.error(`Failed to process ${file}: ${errorMessage}`, { exit: false });
134
+ }
135
+ }
136
+ spinner.succeed(`Processed ${jsonFiles.length} records`);
137
+ return result;
138
+ }
139
+ async pushRecord(recordData, entityName, baseDir, fileName, defaults, syncEngine, dryRun, verbose = false) {
140
+ // Load or create entity
141
+ let entity = null;
142
+ let isNew = false;
143
+ if (recordData.primaryKey) {
144
+ entity = await syncEngine.loadEntity(entityName, recordData.primaryKey);
145
+ }
146
+ if (!entity) {
147
+ // New record
148
+ entity = await syncEngine.createEntityObject(entityName);
149
+ entity.NewRecord();
150
+ isNew = true;
151
+ }
152
+ // Apply defaults first
153
+ for (const [field, value] of Object.entries(defaults)) {
154
+ if (field in entity) {
155
+ entity[field] = value;
156
+ }
157
+ }
158
+ // Apply record fields
159
+ for (const [field, value] of Object.entries(recordData.fields)) {
160
+ if (field in entity) {
161
+ try {
162
+ const processedValue = await syncEngine.processFieldValue(value, baseDir, null, null);
163
+ if (verbose) {
164
+ this.log(` Setting ${field}: ${JSON.stringify(value)} -> ${JSON.stringify(processedValue)}`);
165
+ }
166
+ entity[field] = processedValue;
167
+ }
168
+ catch (error) {
169
+ throw new Error(`Failed to process field '${field}': ${error}`);
170
+ }
171
+ }
172
+ else {
173
+ this.warn(`Field '${field}' does not exist on entity '${entityName}'`);
174
+ }
175
+ }
176
+ if (dryRun) {
177
+ this.log(`Would ${isNew ? 'create' : 'update'} ${entityName} record`);
178
+ return isNew;
179
+ }
180
+ // Save the record
181
+ const saved = await entity.Save();
182
+ if (!saved) {
183
+ const errors = entity.LatestResult?.Errors?.join(', ') || 'Unknown error';
184
+ throw new Error(`Failed to save record: ${errors}`);
185
+ }
186
+ // Process related entities after saving parent
187
+ if (recordData.relatedEntities && !dryRun) {
188
+ await this.processRelatedEntities(recordData.relatedEntities, entity, entity, // root is same as parent for top level
189
+ baseDir, syncEngine, verbose);
190
+ }
191
+ // Update the local file with new primary key if created
192
+ if (isNew) {
193
+ const entityInfo = syncEngine.getEntityInfo(entityName);
194
+ if (entityInfo) {
195
+ const newPrimaryKey = {};
196
+ for (const pk of entityInfo.PrimaryKeys) {
197
+ newPrimaryKey[pk.Name] = entity.Get(pk.Name);
198
+ }
199
+ recordData.primaryKey = newPrimaryKey;
200
+ }
201
+ }
202
+ // Always update sync metadata and write back to file
203
+ // This ensures related entities are persisted with their metadata
204
+ recordData.sync = {
205
+ lastModified: new Date().toISOString(),
206
+ checksum: syncEngine.calculateChecksum(recordData.fields)
207
+ };
208
+ // Write back to file
209
+ const filePath = path_1.default.join(baseDir, fileName);
210
+ await fs_extra_1.default.writeJson(filePath, recordData, { spaces: 2 });
211
+ return isNew;
212
+ }
213
+ async processRelatedEntities(relatedEntities, parentEntity, rootEntity, baseDir, syncEngine, verbose = false, indentLevel = 1) {
214
+ const indent = ' '.repeat(indentLevel);
215
+ for (const [entityName, records] of Object.entries(relatedEntities)) {
216
+ this.log(`${indent}↳ Processing ${records.length} related ${entityName} records`);
217
+ for (const relatedRecord of records) {
218
+ try {
219
+ // Load or create entity
220
+ let entity = null;
221
+ let isNew = false;
222
+ if (relatedRecord.primaryKey) {
223
+ entity = await syncEngine.loadEntity(entityName, relatedRecord.primaryKey);
224
+ }
225
+ if (!entity) {
226
+ entity = await syncEngine.createEntityObject(entityName);
227
+ entity.NewRecord();
228
+ isNew = true;
229
+ }
230
+ // Apply fields with parent/root context
231
+ for (const [field, value] of Object.entries(relatedRecord.fields)) {
232
+ if (field in entity) {
233
+ try {
234
+ const processedValue = await syncEngine.processFieldValue(value, baseDir, parentEntity, rootEntity);
235
+ if (verbose) {
236
+ this.log(`${indent} Setting ${field}: ${JSON.stringify(value)} -> ${JSON.stringify(processedValue)}`);
237
+ }
238
+ entity[field] = processedValue;
239
+ }
240
+ catch (error) {
241
+ throw new Error(`Failed to process field '${field}' in ${entityName}: ${error}`);
242
+ }
243
+ }
244
+ else {
245
+ this.warn(`${indent} Field '${field}' does not exist on entity '${entityName}'`);
246
+ }
247
+ }
248
+ // Save the related entity
249
+ const saved = await entity.Save();
250
+ if (!saved) {
251
+ const errors = entity.LatestResult?.Errors?.join(', ') || 'Unknown error';
252
+ throw new Error(`Failed to save related ${entityName}: ${errors}`);
253
+ }
254
+ if (verbose) {
255
+ this.log(`${indent} ✓ ${isNew ? 'Created' : 'Updated'} ${entityName} record`);
256
+ }
257
+ // Update the related record with primary key and sync metadata
258
+ const entityInfo = syncEngine.getEntityInfo(entityName);
259
+ if (entityInfo) {
260
+ // Update primary key if new
261
+ if (isNew) {
262
+ relatedRecord.primaryKey = {};
263
+ for (const pk of entityInfo.PrimaryKeys) {
264
+ relatedRecord.primaryKey[pk.Name] = entity.Get(pk.Name);
265
+ }
266
+ }
267
+ // Always update sync metadata
268
+ relatedRecord.sync = {
269
+ lastModified: new Date().toISOString(),
270
+ checksum: syncEngine.calculateChecksum(relatedRecord.fields)
271
+ };
272
+ }
273
+ // Process nested related entities if any
274
+ if (relatedRecord.relatedEntities) {
275
+ await this.processRelatedEntities(relatedRecord.relatedEntities, entity, rootEntity, baseDir, syncEngine, verbose, indentLevel + 1);
276
+ }
277
+ }
278
+ catch (error) {
279
+ throw new Error(`Failed to process related ${entityName}: ${error}`);
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ exports.default = Push;
286
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/push/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 { confirm } from '@inquirer/prompts';\nimport ora from 'ora-classic';\nimport fastGlob from 'fast-glob';\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';\nimport { cleanupProvider } from '../../lib/provider-utils';\n\nexport default class Push extends Command {\n static description = 'Push local file changes to the database';\n \n static examples = [\n `<%= config.bin %> <%= command.id %>`,\n `<%= config.bin %> <%= command.id %> --dry-run`,\n `<%= config.bin %> <%= command.id %> --dir=\"ai-prompts\"`,\n `<%= config.bin %> <%= command.id %> --ci`,\n ];\n \n static flags = {\n dir: Flags.string({ description: 'Specific entity directory to push' }),\n 'dry-run': Flags.boolean({ description: 'Show what would be pushed without actually pushing' }),\n ci: Flags.boolean({ description: 'CI mode - no prompts, fail on issues' }),\n verbose: Flags.boolean({ char: 'v', description: 'Show detailed field-level output' }),\n };\n \n async run(): Promise<void> {\n const { flags } = await this.parse(Push);\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 const syncConfig = await loadSyncConfig(process.cwd());\n \n // Initialize data provider\n await initializeProvider(mjConfig);\n \n // Initialize sync engine\n const syncEngine = new SyncEngine(getSystemUser());\n await syncEngine.initialize();\n spinner.succeed('Configuration loaded');\n \n // Find entity directories to process\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(`Found ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} to process`);\n \n // Process each entity directory\n let totalCreated = 0;\n let totalUpdated = 0;\n let totalErrors = 0;\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(`\\nProcessing ${entityConfig.entity} in ${entityDir}`);\n \n const result = await this.processEntityDirectory(\n entityDir,\n entityConfig,\n syncEngine,\n flags,\n syncConfig\n );\n \n totalCreated += result.created;\n totalUpdated += result.updated;\n totalErrors += result.errors;\n }\n \n // Summary\n this.log('\\n=== Push Summary ===');\n this.log(`Created: ${totalCreated}`);\n this.log(`Updated: ${totalUpdated}`);\n this.log(`Errors: ${totalErrors}`);\n \n if (totalErrors > 0 && flags.ci) {\n this.error('Push failed with errors in CI mode');\n }\n \n } catch (error) {\n spinner.fail('Push failed');\n this.error(error as Error);\n } finally {\n // Clean up database connection\n await cleanupProvider();\n }\n }\n \n private async processEntityDirectory(\n entityDir: string,\n entityConfig: any,\n syncEngine: SyncEngine,\n flags: any,\n syncConfig: any\n ): Promise<{ created: number; updated: number; errors: number }> {\n const result = { created: 0, updated: 0, errors: 0 };\n \n // Find all JSON files\n const pattern = entityConfig.filePattern || '*.json';\n const jsonFiles = await fastGlob(pattern, {\n cwd: entityDir,\n ignore: ['.mj-sync.json', '.mj-folder.json']\n });\n \n this.log(`Found ${jsonFiles.length} records to process`);\n \n if (jsonFiles.length === 0) {\n return result;\n }\n \n // Confirm if needed\n if (!flags['dry-run'] && !flags.ci && syncConfig?.push?.requireConfirmation) {\n const proceed = await confirm({\n message: `Push ${jsonFiles.length} ${entityConfig.entity} records to database?`\n });\n \n if (!proceed) {\n this.log('Push cancelled');\n return result;\n }\n }\n \n // Process each file\n const spinner = ora();\n spinner.start('Processing records');\n \n for (const file of jsonFiles) {\n try {\n const filePath = path.join(entityDir, file);\n const recordData: RecordData = await fs.readJson(filePath);\n \n // Build and process defaults (including lookups)\n const defaults = await syncEngine.buildDefaults(filePath, entityConfig);\n \n // Process the record\n const isNew = await this.pushRecord(\n recordData,\n entityConfig.entity,\n path.dirname(filePath),\n file,\n defaults,\n syncEngine,\n flags['dry-run'],\n flags.verbose\n );\n \n if (!flags['dry-run']) {\n if (isNew) {\n result.created++;\n } else {\n result.updated++;\n }\n }\n \n spinner.text = `Processing records (${result.created + result.updated + result.errors}/${jsonFiles.length})`;\n \n } catch (error) {\n result.errors++;\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.error(`Failed to process ${file}: ${errorMessage}`, { exit: false });\n }\n }\n \n spinner.succeed(`Processed ${jsonFiles.length} records`);\n return result;\n }\n \n private async pushRecord(\n recordData: RecordData,\n entityName: string,\n baseDir: string,\n fileName: string,\n defaults: Record<string, any>,\n syncEngine: SyncEngine,\n dryRun: boolean,\n verbose: boolean = false\n ): Promise<boolean> {\n // Load or create entity\n let entity: BaseEntity | null = null;\n let isNew = false;\n \n if (recordData.primaryKey) {\n entity = await syncEngine.loadEntity(entityName, recordData.primaryKey);\n }\n \n if (!entity) {\n // New record\n entity = await syncEngine.createEntityObject(entityName);\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 try {\n const processedValue = await syncEngine.processFieldValue(value, baseDir, null, null);\n if (verbose) {\n this.log(` Setting ${field}: ${JSON.stringify(value)} -> ${JSON.stringify(processedValue)}`);\n }\n (entity as any)[field] = processedValue;\n } catch (error) {\n throw new Error(`Failed to process field '${field}': ${error}`);\n }\n } else {\n this.warn(`Field '${field}' does not exist on entity '${entityName}'`);\n }\n }\n \n if (dryRun) {\n this.log(`Would ${isNew ? 'create' : 'update'} ${entityName} record`);\n return isNew;\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 // Process related entities after saving parent\n if (recordData.relatedEntities && !dryRun) {\n await this.processRelatedEntities(\n recordData.relatedEntities,\n entity,\n entity, // root is same as parent for top level\n baseDir,\n syncEngine,\n verbose\n );\n }\n \n // Update the local file with new primary key if created\n if (isNew) {\n const entityInfo = syncEngine.getEntityInfo(entityName);\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 }\n \n // Always update sync metadata and write back to file\n // This ensures related entities are persisted with their metadata\n recordData.sync = {\n lastModified: new Date().toISOString(),\n checksum: syncEngine.calculateChecksum(recordData.fields)\n };\n \n // Write back to file\n const filePath = path.join(baseDir, fileName);\n await fs.writeJson(filePath, recordData, { spaces: 2 });\n \n return isNew;\n }\n \n private async processRelatedEntities(\n relatedEntities: Record<string, RecordData[]>,\n parentEntity: BaseEntity,\n rootEntity: BaseEntity,\n baseDir: string,\n syncEngine: SyncEngine,\n verbose: boolean = false,\n indentLevel: number = 1\n ): Promise<void> {\n const indent = ' '.repeat(indentLevel);\n \n for (const [entityName, records] of Object.entries(relatedEntities)) {\n this.log(`${indent}↳ Processing ${records.length} related ${entityName} records`);\n \n for (const relatedRecord of records) {\n try {\n // Load or create entity\n let entity = null;\n let isNew = false;\n \n if (relatedRecord.primaryKey) {\n entity = await syncEngine.loadEntity(entityName, relatedRecord.primaryKey);\n }\n \n if (!entity) {\n entity = await syncEngine.createEntityObject(entityName);\n entity.NewRecord();\n isNew = true;\n }\n \n // Apply fields with parent/root context\n for (const [field, value] of Object.entries(relatedRecord.fields)) {\n if (field in entity) {\n try {\n const processedValue = await syncEngine.processFieldValue(\n value, \n baseDir, \n parentEntity, \n rootEntity\n );\n if (verbose) {\n this.log(`${indent} Setting ${field}: ${JSON.stringify(value)} -> ${JSON.stringify(processedValue)}`);\n }\n (entity as any)[field] = processedValue;\n } catch (error) {\n throw new Error(`Failed to process field '${field}' in ${entityName}: ${error}`);\n }\n } else {\n this.warn(`${indent} Field '${field}' does not exist on entity '${entityName}'`);\n }\n }\n \n // Save the related entity\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 related ${entityName}: ${errors}`);\n }\n \n if (verbose) {\n this.log(`${indent} ✓ ${isNew ? 'Created' : 'Updated'} ${entityName} record`);\n }\n \n // Update the related record with primary key and sync metadata\n const entityInfo = syncEngine.getEntityInfo(entityName);\n if (entityInfo) {\n // Update primary key if new\n if (isNew) {\n relatedRecord.primaryKey = {};\n for (const pk of entityInfo.PrimaryKeys) {\n relatedRecord.primaryKey[pk.Name] = entity.Get(pk.Name);\n }\n }\n \n // Always update sync metadata\n relatedRecord.sync = {\n lastModified: new Date().toISOString(),\n checksum: syncEngine.calculateChecksum(relatedRecord.fields)\n };\n }\n \n // Process nested related entities if any\n if (relatedRecord.relatedEntities) {\n await this.processRelatedEntities(\n relatedRecord.relatedEntities,\n entity,\n rootEntity,\n baseDir,\n syncEngine,\n verbose,\n indentLevel + 1\n );\n }\n } catch (error) {\n throw new Error(`Failed to process related ${entityName}: ${error}`);\n }\n }\n }\n }\n}"]}
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Status extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ dir: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ };
8
+ run(): Promise<void>;
9
+ private checkEntityDirectory;
10
+ }
@@ -0,0 +1,124 @@
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 fast_glob_1 = __importDefault(require("fast-glob"));
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 Status extends core_1.Command {
15
+ static description = 'Show status of local files vs 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 check status' }),
22
+ };
23
+ async run() {
24
+ const { flags } = await this.parse(Status);
25
+ const spinner = (0, ora_classic_1.default)();
26
+ try {
27
+ // Load configurations
28
+ spinner.start('Loading configuration');
29
+ const mjConfig = (0, config_1.loadMJConfig)();
30
+ if (!mjConfig) {
31
+ this.error('No mj.config.cjs found in current directory or parent directories');
32
+ }
33
+ // Initialize data provider
34
+ const provider = await (0, provider_utils_1.initializeProvider)(mjConfig);
35
+ // Initialize sync engine
36
+ const syncEngine = new sync_engine_1.SyncEngine((0, provider_utils_1.getSystemUser)());
37
+ await syncEngine.initialize();
38
+ spinner.succeed('Configuration loaded');
39
+ // Find entity directories to process
40
+ const entityDirs = (0, provider_utils_1.findEntityDirectories)(process.cwd(), flags.dir);
41
+ if (entityDirs.length === 0) {
42
+ this.error('No entity directories found');
43
+ }
44
+ this.log(`Found ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} to check`);
45
+ // Process each entity directory
46
+ let totalNew = 0;
47
+ let totalModified = 0;
48
+ let totalDeleted = 0;
49
+ let totalUnchanged = 0;
50
+ for (const entityDir of entityDirs) {
51
+ const entityConfig = await (0, config_1.loadEntityConfig)(entityDir);
52
+ if (!entityConfig) {
53
+ this.warn(`Skipping ${entityDir} - no valid entity configuration`);
54
+ continue;
55
+ }
56
+ this.log(`\nChecking ${entityConfig.entity} in ${entityDir}`);
57
+ const result = await this.checkEntityDirectory(entityDir, entityConfig, syncEngine);
58
+ totalNew += result.new;
59
+ totalModified += result.modified;
60
+ totalDeleted += result.deleted;
61
+ totalUnchanged += result.unchanged;
62
+ // Show directory summary
63
+ if (result.new > 0 || result.modified > 0 || result.deleted > 0) {
64
+ this.log(` New: ${result.new}, Modified: ${result.modified}, Deleted: ${result.deleted}, Unchanged: ${result.unchanged}`);
65
+ }
66
+ else {
67
+ this.log(` All ${result.unchanged} records are up to date`);
68
+ }
69
+ }
70
+ // Overall summary
71
+ this.log('\n=== Status Summary ===');
72
+ this.log(`New (local only): ${totalNew}`);
73
+ this.log(`Modified locally: ${totalModified}`);
74
+ this.log(`Deleted locally: ${totalDeleted}`);
75
+ this.log(`Unchanged: ${totalUnchanged}`);
76
+ }
77
+ catch (error) {
78
+ spinner.fail('Status check failed');
79
+ this.error(error);
80
+ }
81
+ }
82
+ async checkEntityDirectory(entityDir, entityConfig, syncEngine) {
83
+ const result = { new: 0, modified: 0, deleted: 0, unchanged: 0 };
84
+ // Find all JSON files
85
+ const pattern = entityConfig.filePattern || '*.json';
86
+ const jsonFiles = await (0, fast_glob_1.default)(pattern, {
87
+ cwd: entityDir,
88
+ ignore: ['.mj-sync.json', '.mj-folder.json']
89
+ });
90
+ for (const file of jsonFiles) {
91
+ try {
92
+ const filePath = path_1.default.join(entityDir, file);
93
+ const recordData = await fs_extra_1.default.readJson(filePath);
94
+ if (recordData.primaryKey) {
95
+ // Check if record exists in database
96
+ const entity = await syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);
97
+ if (!entity) {
98
+ result.deleted++;
99
+ }
100
+ else {
101
+ // Check if modified
102
+ const currentChecksum = syncEngine.calculateChecksum(recordData.fields);
103
+ if (recordData.sync?.checksum !== currentChecksum) {
104
+ result.modified++;
105
+ }
106
+ else {
107
+ result.unchanged++;
108
+ }
109
+ }
110
+ }
111
+ else {
112
+ // New record
113
+ result.new++;
114
+ }
115
+ }
116
+ catch (error) {
117
+ this.warn(`Failed to check ${file}: ${error}`);
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ }
123
+ exports.default = Status;
124
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/status/index.ts"],"names":[],"mappings":";;;;;AAAA,sCAA6C;AAC7C,wDAA0B;AAC1B,gDAAwB;AACxB,0DAAiC;AACjC,8DAA8B;AAC9B,yCAA8D;AAC9D,uDAA+D;AAC/D,6DAAoG;AAEpG,MAAqB,MAAO,SAAQ,cAAO;IACzC,MAAM,CAAC,WAAW,GAAG,wCAAwC,CAAC;IAE9D,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,2CAA2C,EAAE,CAAC;KAChF,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3C,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,2BAA2B;YAC3B,MAAM,QAAQ,GAAG,MAAM,IAAA,mCAAkB,EAAC,QAAQ,CAAC,CAAC;YAEpD,yBAAyB;YACzB,MAAM,UAAU,GAAG,IAAI,wBAAU,CAAC,IAAA,8BAAa,GAAE,CAAC,CAAC;YACnD,MAAM,UAAU,CAAC,UAAU,EAAE,CAAC;YAC9B,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;YAExC,qCAAqC;YACrC,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,SAAS,UAAU,CAAC,MAAM,WAAW,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,WAAW,CAAC,CAAC;YAEhH,gCAAgC;YAChC,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,IAAI,aAAa,GAAG,CAAC,CAAC;YACtB,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,IAAI,cAAc,GAAG,CAAC,CAAC;YAEvB,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,cAAc,YAAY,CAAC,MAAM,OAAO,SAAS,EAAE,CAAC,CAAC;gBAE9D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAC5C,SAAS,EACT,YAAY,EACZ,UAAU,CACX,CAAC;gBAEF,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC;gBACvB,aAAa,IAAI,MAAM,CAAC,QAAQ,CAAC;gBACjC,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;gBAC/B,cAAc,IAAI,MAAM,CAAC,SAAS,CAAC;gBAEnC,yBAAyB;gBACzB,IAAI,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;oBAChE,IAAI,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,GAAG,eAAe,MAAM,CAAC,QAAQ,cAAc,MAAM,CAAC,OAAO,gBAAgB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;gBAC7H,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,SAAS,yBAAyB,CAAC,CAAC;gBAC/D,CAAC;YACH,CAAC;YAED,kBAAkB;YAClB,IAAI,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC,GAAG,CAAC,qBAAqB,aAAa,EAAE,CAAC,CAAC;YAC/C,IAAI,CAAC,GAAG,CAAC,oBAAoB,YAAY,EAAE,CAAC,CAAC;YAC7C,IAAI,CAAC,GAAG,CAAC,cAAc,cAAc,EAAE,CAAC,CAAC;QAE3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACpC,IAAI,CAAC,KAAK,CAAC,KAAc,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAChC,SAAiB,EACjB,YAAiB,EACjB,UAAsB;QAEtB,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QAEjE,sBAAsB;QACtB,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,IAAI,QAAQ,CAAC;QACrD,MAAM,SAAS,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE;YACxC,GAAG,EAAE,SAAS;YACd,MAAM,EAAE,CAAC,eAAe,EAAE,iBAAiB,CAAC;SAC7C,CAAC,CAAC;QAEH,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBAC5C,MAAM,UAAU,GAAe,MAAM,kBAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAE3D,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;oBAC1B,qCAAqC;oBACrC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;oBAEvF,IAAI,CAAC,MAAM,EAAE,CAAC;wBACZ,MAAM,CAAC,OAAO,EAAE,CAAC;oBACnB,CAAC;yBAAM,CAAC;wBACN,oBAAoB;wBACpB,MAAM,eAAe,GAAG,UAAU,CAAC,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;wBACxE,IAAI,UAAU,CAAC,IAAI,EAAE,QAAQ,KAAK,eAAe,EAAE,CAAC;4BAClD,MAAM,CAAC,QAAQ,EAAE,CAAC;wBACpB,CAAC;6BAAM,CAAC;4BACN,MAAM,CAAC,SAAS,EAAE,CAAC;wBACrB,CAAC;oBACH,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,aAAa;oBACb,MAAM,CAAC,GAAG,EAAE,CAAC;gBACf,CAAC;YAEH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,mBAAmB,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;;AArIH,yBAsIC","sourcesContent":["import { Command, Flags } from '@oclif/core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport fastGlob from 'fast-glob';\nimport ora from 'ora-classic';\nimport { loadMJConfig, loadEntityConfig } from '../../config';\nimport { SyncEngine, RecordData } from '../../lib/sync-engine';\nimport { initializeProvider, findEntityDirectories, getSystemUser } from '../../lib/provider-utils';\n\nexport default class Status extends Command {\n static description = 'Show status of local files vs 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 check status' }),\n };\n \n async run(): Promise<void> {\n const { flags } = await this.parse(Status);\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 // Initialize data provider\n const provider = await initializeProvider(mjConfig);\n \n // Initialize sync engine\n const syncEngine = new SyncEngine(getSystemUser());\n await syncEngine.initialize();\n spinner.succeed('Configuration loaded');\n \n // Find entity directories to process\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(`Found ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} to check`);\n \n // Process each entity directory\n let totalNew = 0;\n let totalModified = 0;\n let totalDeleted = 0;\n let totalUnchanged = 0;\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(`\\nChecking ${entityConfig.entity} in ${entityDir}`);\n \n const result = await this.checkEntityDirectory(\n entityDir,\n entityConfig,\n syncEngine\n );\n \n totalNew += result.new;\n totalModified += result.modified;\n totalDeleted += result.deleted;\n totalUnchanged += result.unchanged;\n \n // Show directory summary\n if (result.new > 0 || result.modified > 0 || result.deleted > 0) {\n this.log(` New: ${result.new}, Modified: ${result.modified}, Deleted: ${result.deleted}, Unchanged: ${result.unchanged}`);\n } else {\n this.log(` All ${result.unchanged} records are up to date`);\n }\n }\n \n // Overall summary\n this.log('\\n=== Status Summary ===');\n this.log(`New (local only): ${totalNew}`);\n this.log(`Modified locally: ${totalModified}`);\n this.log(`Deleted locally: ${totalDeleted}`);\n this.log(`Unchanged: ${totalUnchanged}`);\n \n } catch (error) {\n spinner.fail('Status check failed');\n this.error(error as Error);\n }\n }\n \n private async checkEntityDirectory(\n entityDir: string,\n entityConfig: any,\n syncEngine: SyncEngine\n ): Promise<{ new: number; modified: number; deleted: number; unchanged: number }> {\n const result = { new: 0, modified: 0, deleted: 0, unchanged: 0 };\n \n // Find all JSON files\n const pattern = entityConfig.filePattern || '*.json';\n const jsonFiles = await fastGlob(pattern, {\n cwd: entityDir,\n ignore: ['.mj-sync.json', '.mj-folder.json']\n });\n \n for (const file of jsonFiles) {\n try {\n const filePath = path.join(entityDir, file);\n const recordData: RecordData = await fs.readJson(filePath);\n \n if (recordData.primaryKey) {\n // Check if record exists in database\n const entity = await syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);\n \n if (!entity) {\n result.deleted++;\n } else {\n // Check if modified\n const currentChecksum = syncEngine.calculateChecksum(recordData.fields);\n if (recordData.sync?.checksum !== currentChecksum) {\n result.modified++;\n } else {\n result.unchanged++;\n }\n }\n } else {\n // New record\n result.new++;\n }\n \n } catch (error) {\n this.warn(`Failed to check ${file}: ${error}`);\n }\n }\n \n return result;\n }\n}"]}
@@ -0,0 +1,15 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Watch extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ dir: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ };
8
+ private syncEngine;
9
+ private syncConfig;
10
+ private debounceTimers;
11
+ run(): Promise<void>;
12
+ private handleFileChange;
13
+ private syncJsonFile;
14
+ private syncExternalFile;
15
+ }