@memberjunction/cli 2.67.0 → 2.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/sync/pull.d.ts +9 -0
- package/dist/commands/sync/pull.js +163 -76
- package/oclif.manifest.json +65 -2
- package/package.json +4 -4
|
@@ -9,7 +9,16 @@ export default class Pull extends Command {
|
|
|
9
9
|
'multi-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
10
|
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
'no-validate': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
'update-existing': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'create-new': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'backup-before-update': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
'merge-strategy': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
|
+
'backup-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
|
+
'preserve-fields': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
|
+
'exclude-fields': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
+
'target-dir': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
20
|
};
|
|
13
21
|
run(): Promise<void>;
|
|
14
22
|
private findEntityDirectories;
|
|
23
|
+
private findAllEntityDirectoriesRecursive;
|
|
15
24
|
}
|
|
@@ -1,27 +1,4 @@
|
|
|
1
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
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
4
|
};
|
|
@@ -36,6 +13,10 @@ class Pull extends core_1.Command {
|
|
|
36
13
|
static examples = [
|
|
37
14
|
`<%= config.bin %> <%= command.id %> --entity="AI Prompts"`,
|
|
38
15
|
`<%= config.bin %> <%= command.id %> --entity="AI Prompts" --filter="CategoryID='customer-service-id'"`,
|
|
16
|
+
`<%= config.bin %> <%= command.id %> --entity="AI Agents" --merge-strategy=overwrite`,
|
|
17
|
+
`<%= config.bin %> <%= command.id %> --entity="Actions" --target-dir=./custom-actions --no-validate`,
|
|
18
|
+
`<%= config.bin %> <%= command.id %> --entity="Templates" --dry-run --verbose`,
|
|
19
|
+
`<%= config.bin %> <%= command.id %> --entity="AI Prompts" --exclude-fields=InternalNotes,DebugInfo`,
|
|
39
20
|
];
|
|
40
21
|
static flags = {
|
|
41
22
|
entity: core_1.Flags.string({ description: 'Entity name to pull', required: true }),
|
|
@@ -44,10 +25,23 @@ class Pull extends core_1.Command {
|
|
|
44
25
|
'multi-file': core_1.Flags.string({ description: 'Create a single file with multiple records (provide filename)' }),
|
|
45
26
|
verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed output' }),
|
|
46
27
|
'no-validate': core_1.Flags.boolean({ description: 'Skip validation before pull' }),
|
|
28
|
+
'update-existing': core_1.Flags.boolean({ description: 'Update existing records during pull', default: true }),
|
|
29
|
+
'create-new': core_1.Flags.boolean({ description: 'Create new files for records not found locally', default: true }),
|
|
30
|
+
'backup-before-update': core_1.Flags.boolean({ description: 'Create backups before updating files', default: true }),
|
|
31
|
+
'merge-strategy': core_1.Flags.string({
|
|
32
|
+
description: 'Merge strategy for updates',
|
|
33
|
+
options: ['merge', 'overwrite', 'skip'],
|
|
34
|
+
default: 'merge'
|
|
35
|
+
}),
|
|
36
|
+
'backup-directory': core_1.Flags.string({ description: 'Custom backup directory (default: .backups)' }),
|
|
37
|
+
'preserve-fields': core_1.Flags.string({ description: 'Comma-separated list of fields to preserve during updates', multiple: true }),
|
|
38
|
+
'exclude-fields': core_1.Flags.string({ description: 'Comma-separated list of fields to exclude from pull', multiple: true }),
|
|
39
|
+
'target-dir': core_1.Flags.string({ description: 'Specific target directory (overrides auto-discovery)' })
|
|
47
40
|
};
|
|
48
41
|
async run() {
|
|
49
42
|
const { flags } = await this.parse(Pull);
|
|
50
43
|
const spinner = (0, ora_classic_1.default)();
|
|
44
|
+
let backupManager = null;
|
|
51
45
|
try {
|
|
52
46
|
// Load MJ config first
|
|
53
47
|
spinner.start('Loading configuration');
|
|
@@ -68,12 +62,30 @@ class Pull extends core_1.Command {
|
|
|
68
62
|
else {
|
|
69
63
|
spinner.stop();
|
|
70
64
|
}
|
|
71
|
-
//
|
|
65
|
+
// Find entity directories
|
|
66
|
+
const entityDirectories = await this.findEntityDirectories(flags.entity);
|
|
67
|
+
if (entityDirectories.length === 0) {
|
|
68
|
+
this.error(`No directories found for entity "${flags.entity}". Make sure the entity configuration exists in a .mj-sync.json file.`);
|
|
69
|
+
}
|
|
70
|
+
// Select target directory
|
|
71
|
+
let targetDir = flags['target-dir'];
|
|
72
|
+
if (!targetDir) {
|
|
73
|
+
if (entityDirectories.length === 1) {
|
|
74
|
+
targetDir = entityDirectories[0];
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
targetDir = await (0, prompts_1.select)({
|
|
78
|
+
message: `Multiple directories found for entity "${flags.entity}". Which one to use?`,
|
|
79
|
+
choices: entityDirectories.map(dir => ({ name: dir, value: dir }))
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Run validation on target directory unless disabled
|
|
72
84
|
if (!flags['no-validate']) {
|
|
73
|
-
spinner.start('Validating
|
|
85
|
+
spinner.start('Validating target directory...');
|
|
74
86
|
const validator = new metadata_sync_1.ValidationService({ verbose: flags.verbose });
|
|
75
87
|
const formatter = new metadata_sync_1.FormattingService();
|
|
76
|
-
const validationResult = await validator.validateDirectory(
|
|
88
|
+
const validationResult = await validator.validateDirectory(targetDir);
|
|
77
89
|
spinner.stop();
|
|
78
90
|
if (!validationResult.isValid || validationResult.warnings.length > 0) {
|
|
79
91
|
// Show validation results
|
|
@@ -97,62 +109,94 @@ class Pull extends core_1.Command {
|
|
|
97
109
|
this.log(chalk_1.default.green('✓ Validation passed'));
|
|
98
110
|
}
|
|
99
111
|
}
|
|
100
|
-
//
|
|
101
|
-
|
|
112
|
+
// Initialize backup manager if backups are enabled
|
|
113
|
+
if (flags['backup-before-update']) {
|
|
114
|
+
backupManager = new metadata_sync_1.FileBackupManager();
|
|
115
|
+
await backupManager.initialize();
|
|
116
|
+
}
|
|
102
117
|
// Create pull service and execute
|
|
103
118
|
const pullService = new metadata_sync_1.PullService(syncEngine, (0, metadata_sync_1.getSystemUser)());
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
119
|
+
// Build pull options - only include CLI flags that were explicitly provided
|
|
120
|
+
const pullOptions = {
|
|
121
|
+
entity: flags.entity,
|
|
122
|
+
targetDir,
|
|
123
|
+
dryRun: flags['dry-run'],
|
|
124
|
+
verbose: flags.verbose,
|
|
125
|
+
noValidate: flags['no-validate']
|
|
126
|
+
};
|
|
127
|
+
// Add optional flags only if explicitly provided to avoid overriding entity config
|
|
128
|
+
if (flags.filter !== undefined)
|
|
129
|
+
pullOptions.filter = flags.filter;
|
|
130
|
+
if (flags['multi-file'] !== undefined)
|
|
131
|
+
pullOptions.multiFile = flags['multi-file'];
|
|
132
|
+
if (flags['update-existing'] !== undefined)
|
|
133
|
+
pullOptions.updateExistingRecords = flags['update-existing'];
|
|
134
|
+
if (flags['create-new'] !== undefined)
|
|
135
|
+
pullOptions.createNewFileIfNotFound = flags['create-new'];
|
|
136
|
+
if (flags['backup-before-update'] !== undefined)
|
|
137
|
+
pullOptions.backupBeforeUpdate = flags['backup-before-update'];
|
|
138
|
+
if (flags['merge-strategy'] !== undefined)
|
|
139
|
+
pullOptions.mergeStrategy = flags['merge-strategy'];
|
|
140
|
+
if (flags['backup-directory'] !== undefined)
|
|
141
|
+
pullOptions.backupDirectory = flags['backup-directory'];
|
|
142
|
+
if (flags['preserve-fields'] !== undefined)
|
|
143
|
+
pullOptions.preserveFields = flags['preserve-fields'];
|
|
144
|
+
if (flags['exclude-fields'] !== undefined)
|
|
145
|
+
pullOptions.excludeFields = flags['exclude-fields'];
|
|
146
|
+
await pullService.pull(pullOptions, {
|
|
147
|
+
onProgress: (message) => {
|
|
148
|
+
spinner.start(message);
|
|
149
|
+
},
|
|
150
|
+
onSuccess: (message) => {
|
|
151
|
+
spinner.succeed(message);
|
|
152
|
+
},
|
|
153
|
+
onError: (message) => {
|
|
154
|
+
spinner.fail(message);
|
|
155
|
+
},
|
|
156
|
+
onWarn: (message) => {
|
|
157
|
+
this.warn(message);
|
|
158
|
+
},
|
|
159
|
+
onLog: (message) => {
|
|
160
|
+
this.log(message);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
// Clean up backups on success
|
|
164
|
+
if (backupManager && !flags['dry-run']) {
|
|
165
|
+
await backupManager.cleanup();
|
|
166
|
+
if (flags.verbose) {
|
|
167
|
+
this.log(chalk_1.default.green('✓ Temporary backup files cleaned up'));
|
|
132
168
|
}
|
|
133
169
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
170
|
+
// Clean up persistent backup files created by PullService
|
|
171
|
+
if (!flags['dry-run']) {
|
|
172
|
+
try {
|
|
173
|
+
const backupCount = pullService.getCreatedBackupFiles().length;
|
|
174
|
+
await pullService.cleanupBackupFiles();
|
|
175
|
+
if (flags.verbose && backupCount > 0) {
|
|
176
|
+
this.log(chalk_1.default.green(`✓ Cleaned up ${backupCount} persistent backup files`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (cleanupError) {
|
|
180
|
+
this.warn(`Failed to cleanup persistent backup files: ${cleanupError}`);
|
|
139
181
|
}
|
|
140
|
-
|
|
182
|
+
}
|
|
183
|
+
if (!flags['dry-run']) {
|
|
184
|
+
this.log(`\n✅ Pull completed successfully`);
|
|
141
185
|
}
|
|
142
186
|
}
|
|
143
187
|
catch (error) {
|
|
144
188
|
spinner.fail('Pull failed');
|
|
145
|
-
//
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
189
|
+
// Rollback backups on error
|
|
190
|
+
if (backupManager) {
|
|
191
|
+
try {
|
|
192
|
+
await backupManager.rollback();
|
|
193
|
+
if (flags.verbose) {
|
|
194
|
+
this.log(chalk_1.default.yellow('↩️ Backup files restored'));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (rollbackError) {
|
|
198
|
+
this.warn(`Failed to rollback backup files: ${rollbackError}`);
|
|
199
|
+
}
|
|
156
200
|
}
|
|
157
201
|
// Enhanced error logging
|
|
158
202
|
this.log('\n=== Pull Error Details ===');
|
|
@@ -177,9 +221,52 @@ class Pull extends core_1.Command {
|
|
|
177
221
|
}
|
|
178
222
|
}
|
|
179
223
|
async findEntityDirectories(entityName) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
224
|
+
try {
|
|
225
|
+
const workingDir = metadata_sync_1.configManager.getOriginalCwd();
|
|
226
|
+
// Search recursively for all directories with .mj-sync.json files
|
|
227
|
+
const allDirs = this.findAllEntityDirectoriesRecursive(workingDir);
|
|
228
|
+
// Filter directories that match the requested entity
|
|
229
|
+
const entityDirs = [];
|
|
230
|
+
for (const dir of allDirs) {
|
|
231
|
+
try {
|
|
232
|
+
const config = await (0, metadata_sync_1.loadEntityConfig)(dir);
|
|
233
|
+
if (config && config.entity === entityName) {
|
|
234
|
+
entityDirs.push(dir);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
this.warn(`Skipping directory ${dir}: invalid configuration (${error})`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return entityDirs;
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
this.error(`Failed to find entity directories: ${error}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
findAllEntityDirectoriesRecursive(dir) {
|
|
248
|
+
const fs = require('fs');
|
|
249
|
+
const path = require('path');
|
|
250
|
+
const directories = [];
|
|
251
|
+
try {
|
|
252
|
+
// Check if current directory has .mj-sync.json
|
|
253
|
+
const syncConfigPath = path.join(dir, '.mj-sync.json');
|
|
254
|
+
if (fs.existsSync(syncConfigPath)) {
|
|
255
|
+
directories.push(dir);
|
|
256
|
+
}
|
|
257
|
+
// Recursively search subdirectories
|
|
258
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
259
|
+
for (const item of items) {
|
|
260
|
+
if (item.isDirectory() && !item.name.startsWith('.')) {
|
|
261
|
+
const subdirPath = path.join(dir, item.name);
|
|
262
|
+
directories.push(...this.findAllEntityDirectoriesRecursive(subdirPath));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
// Skip directories we can't read
|
|
268
|
+
}
|
|
269
|
+
return directories;
|
|
183
270
|
}
|
|
184
271
|
}
|
|
185
272
|
exports.default = Pull;
|
package/oclif.manifest.json
CHANGED
|
@@ -304,7 +304,11 @@
|
|
|
304
304
|
"description": "Pull metadata from database to local files",
|
|
305
305
|
"examples": [
|
|
306
306
|
"<%= config.bin %> <%= command.id %> --entity=\"AI Prompts\"",
|
|
307
|
-
"<%= config.bin %> <%= command.id %> --entity=\"AI Prompts\" --filter=\"CategoryID='customer-service-id'\""
|
|
307
|
+
"<%= config.bin %> <%= command.id %> --entity=\"AI Prompts\" --filter=\"CategoryID='customer-service-id'\"",
|
|
308
|
+
"<%= config.bin %> <%= command.id %> --entity=\"AI Agents\" --merge-strategy=overwrite",
|
|
309
|
+
"<%= config.bin %> <%= command.id %> --entity=\"Actions\" --target-dir=./custom-actions --no-validate",
|
|
310
|
+
"<%= config.bin %> <%= command.id %> --entity=\"Templates\" --dry-run --verbose",
|
|
311
|
+
"<%= config.bin %> <%= command.id %> --entity=\"AI Prompts\" --exclude-fields=InternalNotes,DebugInfo"
|
|
308
312
|
],
|
|
309
313
|
"flags": {
|
|
310
314
|
"entity": {
|
|
@@ -347,6 +351,65 @@
|
|
|
347
351
|
"name": "no-validate",
|
|
348
352
|
"allowNo": false,
|
|
349
353
|
"type": "boolean"
|
|
354
|
+
},
|
|
355
|
+
"update-existing": {
|
|
356
|
+
"description": "Update existing records during pull",
|
|
357
|
+
"name": "update-existing",
|
|
358
|
+
"allowNo": false,
|
|
359
|
+
"type": "boolean"
|
|
360
|
+
},
|
|
361
|
+
"create-new": {
|
|
362
|
+
"description": "Create new files for records not found locally",
|
|
363
|
+
"name": "create-new",
|
|
364
|
+
"allowNo": false,
|
|
365
|
+
"type": "boolean"
|
|
366
|
+
},
|
|
367
|
+
"backup-before-update": {
|
|
368
|
+
"description": "Create backups before updating files",
|
|
369
|
+
"name": "backup-before-update",
|
|
370
|
+
"allowNo": false,
|
|
371
|
+
"type": "boolean"
|
|
372
|
+
},
|
|
373
|
+
"merge-strategy": {
|
|
374
|
+
"description": "Merge strategy for updates",
|
|
375
|
+
"name": "merge-strategy",
|
|
376
|
+
"default": "merge",
|
|
377
|
+
"hasDynamicHelp": false,
|
|
378
|
+
"multiple": false,
|
|
379
|
+
"options": [
|
|
380
|
+
"merge",
|
|
381
|
+
"overwrite",
|
|
382
|
+
"skip"
|
|
383
|
+
],
|
|
384
|
+
"type": "option"
|
|
385
|
+
},
|
|
386
|
+
"backup-directory": {
|
|
387
|
+
"description": "Custom backup directory (default: .backups)",
|
|
388
|
+
"name": "backup-directory",
|
|
389
|
+
"hasDynamicHelp": false,
|
|
390
|
+
"multiple": false,
|
|
391
|
+
"type": "option"
|
|
392
|
+
},
|
|
393
|
+
"preserve-fields": {
|
|
394
|
+
"description": "Comma-separated list of fields to preserve during updates",
|
|
395
|
+
"name": "preserve-fields",
|
|
396
|
+
"hasDynamicHelp": false,
|
|
397
|
+
"multiple": true,
|
|
398
|
+
"type": "option"
|
|
399
|
+
},
|
|
400
|
+
"exclude-fields": {
|
|
401
|
+
"description": "Comma-separated list of fields to exclude from pull",
|
|
402
|
+
"name": "exclude-fields",
|
|
403
|
+
"hasDynamicHelp": false,
|
|
404
|
+
"multiple": true,
|
|
405
|
+
"type": "option"
|
|
406
|
+
},
|
|
407
|
+
"target-dir": {
|
|
408
|
+
"description": "Specific target directory (overrides auto-discovery)",
|
|
409
|
+
"name": "target-dir",
|
|
410
|
+
"hasDynamicHelp": false,
|
|
411
|
+
"multiple": false,
|
|
412
|
+
"type": "option"
|
|
350
413
|
}
|
|
351
414
|
},
|
|
352
415
|
"hasDynamicHelp": false,
|
|
@@ -577,5 +640,5 @@
|
|
|
577
640
|
]
|
|
578
641
|
}
|
|
579
642
|
},
|
|
580
|
-
"version": "2.
|
|
643
|
+
"version": "2.69.0"
|
|
581
644
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.69.0",
|
|
4
4
|
"description": "MemberJunction command line tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"oclif"
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@inquirer/prompts": "^5.0.1",
|
|
54
|
-
"@memberjunction/codegen-lib": "2.
|
|
55
|
-
"@memberjunction/metadata-sync": "2.
|
|
56
|
-
"@memberjunction/sqlserver-dataprovider": "2.
|
|
54
|
+
"@memberjunction/codegen-lib": "2.69.0",
|
|
55
|
+
"@memberjunction/metadata-sync": "2.69.0",
|
|
56
|
+
"@memberjunction/sqlserver-dataprovider": "2.69.0",
|
|
57
57
|
"@oclif/core": "^3",
|
|
58
58
|
"@oclif/plugin-help": "^6",
|
|
59
59
|
"@oclif/plugin-version": "^2.0.17",
|