@memberjunction/cli 2.55.0 → 2.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/commands/sync/file-reset.d.ts +14 -0
- package/dist/commands/sync/file-reset.js +197 -0
- package/dist/commands/sync/init.d.ts +6 -0
- package/dist/commands/sync/init.js +101 -0
- package/dist/commands/sync/pull.d.ts +15 -0
- package/dist/commands/sync/pull.js +185 -0
- package/dist/commands/sync/push.d.ts +13 -0
- package/dist/commands/sync/push.js +183 -0
- package/dist/commands/sync/status.d.ts +10 -0
- package/dist/commands/sync/status.js +112 -0
- package/dist/commands/sync/validate.d.ts +12 -0
- package/dist/commands/sync/validate.js +131 -0
- package/dist/commands/sync/watch.d.ts +13 -0
- package/dist/commands/sync/watch.js +148 -0
- package/dist/hooks/init.d.ts +7 -0
- package/dist/hooks/init.js +10 -0
- package/oclif.manifest.json +385 -16
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -72,6 +72,40 @@ module.exports = {
|
|
|
72
72
|
|
|
73
73
|
## Commands
|
|
74
74
|
|
|
75
|
+
### `mj sync`
|
|
76
|
+
|
|
77
|
+
Manages MemberJunction metadata synchronization between database and local files. This suite of commands enables version control, IDE-based editing, and CI/CD integration for MJ metadata.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
mj sync [COMMAND] [OPTIONS]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Available sync commands:
|
|
84
|
+
- `validate` - Validate metadata files for correctness
|
|
85
|
+
- `init` - Initialize a directory for metadata sync
|
|
86
|
+
- `pull` - Pull metadata from database to local files
|
|
87
|
+
- `push` - Push local file changes to database
|
|
88
|
+
- `status` - Show status of local vs database metadata
|
|
89
|
+
- `watch` - Watch for changes and auto-sync
|
|
90
|
+
- `file-reset` - Reset file checksums after manual edits
|
|
91
|
+
|
|
92
|
+
For detailed documentation on metadata sync, see the [MetadataSync README](../MetadataSync/README.md).
|
|
93
|
+
|
|
94
|
+
#### Quick Examples:
|
|
95
|
+
```bash
|
|
96
|
+
# Validate all metadata files
|
|
97
|
+
mj sync validate
|
|
98
|
+
|
|
99
|
+
# Pull AI Prompts from database
|
|
100
|
+
mj sync pull --entity="AI Prompts"
|
|
101
|
+
|
|
102
|
+
# Push changes to database
|
|
103
|
+
mj sync push
|
|
104
|
+
|
|
105
|
+
# Watch for changes
|
|
106
|
+
mj sync watch
|
|
107
|
+
```
|
|
108
|
+
|
|
75
109
|
### `mj install`
|
|
76
110
|
|
|
77
111
|
Performs a complete installation of MemberJunction, including:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class FileReset 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
|
+
sections: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const core_1 = require("@oclif/core");
|
|
30
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
31
|
+
const ora_classic_1 = __importDefault(require("ora-classic"));
|
|
32
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
33
|
+
const path = __importStar(require("path"));
|
|
34
|
+
const metadata_sync_1 = require("@memberjunction/metadata-sync");
|
|
35
|
+
class FileReset extends core_1.Command {
|
|
36
|
+
static description = 'Reset file metadata sections';
|
|
37
|
+
static examples = [
|
|
38
|
+
`<%= config.bin %> <%= command.id %>`,
|
|
39
|
+
`<%= config.bin %> <%= command.id %> --dir="ai-prompts"`,
|
|
40
|
+
`<%= config.bin %> <%= command.id %> --sections="dependencies,references"`,
|
|
41
|
+
`<%= config.bin %> <%= command.id %> --all`,
|
|
42
|
+
];
|
|
43
|
+
static flags = {
|
|
44
|
+
dir: core_1.Flags.string({ description: 'Specific entity directory to reset' }),
|
|
45
|
+
sections: core_1.Flags.string({ description: 'Comma-separated list of sections to reset (dependencies,references,related,lookup)' }),
|
|
46
|
+
all: core_1.Flags.boolean({ description: 'Reset all metadata sections' }),
|
|
47
|
+
'dry-run': core_1.Flags.boolean({ description: 'Show what would be reset without actually resetting' }),
|
|
48
|
+
force: core_1.Flags.boolean({ description: 'Skip confirmation prompts' }),
|
|
49
|
+
verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed output' }),
|
|
50
|
+
};
|
|
51
|
+
async run() {
|
|
52
|
+
const { flags } = await this.parse(FileReset);
|
|
53
|
+
const spinner = (0, ora_classic_1.default)();
|
|
54
|
+
try {
|
|
55
|
+
// Load configuration
|
|
56
|
+
spinner.start('Loading configuration');
|
|
57
|
+
const mjConfig = (0, metadata_sync_1.loadMJConfig)();
|
|
58
|
+
if (!mjConfig) {
|
|
59
|
+
this.error('No mj.config.cjs found in current directory or parent directories');
|
|
60
|
+
}
|
|
61
|
+
// Stop spinner before provider initialization
|
|
62
|
+
spinner.stop();
|
|
63
|
+
// Initialize data provider
|
|
64
|
+
await (0, metadata_sync_1.initializeProvider)(mjConfig);
|
|
65
|
+
// Initialize sync engine
|
|
66
|
+
const syncEngine = await (0, metadata_sync_1.getSyncEngine)((0, metadata_sync_1.getSystemUser)());
|
|
67
|
+
// Show success after initialization
|
|
68
|
+
if (flags.verbose) {
|
|
69
|
+
spinner.succeed('Configuration and metadata loaded');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
spinner.stop();
|
|
73
|
+
}
|
|
74
|
+
// Determine sections to reset
|
|
75
|
+
let sectionsToReset = [];
|
|
76
|
+
const availableSections = ['primaryKey', 'sync'];
|
|
77
|
+
if (flags.all) {
|
|
78
|
+
sectionsToReset = availableSections;
|
|
79
|
+
}
|
|
80
|
+
else if (flags.sections) {
|
|
81
|
+
sectionsToReset = flags.sections.split(',').map(s => s.trim());
|
|
82
|
+
// Validate sections
|
|
83
|
+
const invalidSections = sectionsToReset.filter(s => !availableSections.includes(s));
|
|
84
|
+
if (invalidSections.length > 0) {
|
|
85
|
+
this.error(`Invalid sections: ${invalidSections.join(', ')}. Valid sections are: ${availableSections.join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Interactive selection
|
|
90
|
+
sectionsToReset = await (0, prompts_1.checkbox)({
|
|
91
|
+
message: 'Select sections to reset:',
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: 'Primary Key metadata', value: 'primaryKey', checked: false },
|
|
94
|
+
{ name: 'Sync metadata', value: 'sync', checked: false }
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
if (sectionsToReset.length === 0) {
|
|
98
|
+
this.log(chalk_1.default.yellow('No sections selected. Exiting.'));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Determine target directory
|
|
103
|
+
const targetDir = flags.dir
|
|
104
|
+
? path.resolve(metadata_sync_1.configManager.getOriginalCwd(), flags.dir)
|
|
105
|
+
: metadata_sync_1.configManager.getOriginalCwd();
|
|
106
|
+
// Show what will be reset
|
|
107
|
+
this.log(chalk_1.default.bold('\nFile Reset Summary:'));
|
|
108
|
+
this.log(`Target directory: ${path.relative(process.cwd(), targetDir)}`);
|
|
109
|
+
this.log(`Sections to reset: ${sectionsToReset.join(', ')}`);
|
|
110
|
+
this.log(`Mode: ${flags['dry-run'] ? 'Dry run (no changes will be made)' : 'Live (files will be modified)'}`);
|
|
111
|
+
// Confirm unless forced or dry-run
|
|
112
|
+
if (!flags.force && !flags['dry-run']) {
|
|
113
|
+
const shouldContinue = await (0, prompts_1.confirm)({
|
|
114
|
+
message: 'This will reset the selected metadata sections. Continue?',
|
|
115
|
+
default: false
|
|
116
|
+
});
|
|
117
|
+
if (!shouldContinue) {
|
|
118
|
+
this.log(chalk_1.default.yellow('\nFile reset cancelled.'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Create file reset service and execute
|
|
123
|
+
const fileResetService = new metadata_sync_1.FileResetService();
|
|
124
|
+
spinner.start('Resetting file metadata...');
|
|
125
|
+
// Map selected sections to service options
|
|
126
|
+
let serviceSection = 'both';
|
|
127
|
+
if (sectionsToReset.includes('primaryKey') && sectionsToReset.includes('sync')) {
|
|
128
|
+
serviceSection = 'both';
|
|
129
|
+
}
|
|
130
|
+
else if (sectionsToReset.includes('primaryKey')) {
|
|
131
|
+
serviceSection = 'primaryKey';
|
|
132
|
+
}
|
|
133
|
+
else if (sectionsToReset.includes('sync')) {
|
|
134
|
+
serviceSection = 'sync';
|
|
135
|
+
}
|
|
136
|
+
const result = await fileResetService.resetFiles({
|
|
137
|
+
sections: serviceSection,
|
|
138
|
+
dryRun: flags['dry-run'],
|
|
139
|
+
noBackup: false,
|
|
140
|
+
verbose: flags.verbose
|
|
141
|
+
}, {
|
|
142
|
+
onProgress: (message) => {
|
|
143
|
+
spinner.start(message);
|
|
144
|
+
},
|
|
145
|
+
onWarn: (message) => {
|
|
146
|
+
this.warn(message);
|
|
147
|
+
},
|
|
148
|
+
onLog: (message) => {
|
|
149
|
+
this.log(message);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
spinner.stop();
|
|
153
|
+
// Show summary
|
|
154
|
+
if (result.modifiedFiles > 0) {
|
|
155
|
+
if (flags['dry-run']) {
|
|
156
|
+
this.log(chalk_1.default.blue(`\n🔍 Dry run complete. ${result.modifiedFiles} file(s) would be reset.`));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.log(chalk_1.default.green(`\n✅ Successfully reset ${result.modifiedFiles} file(s).`));
|
|
160
|
+
if (result.backupsCreated > 0) {
|
|
161
|
+
this.log(chalk_1.default.gray(` 📁 Created ${result.backupsCreated} backup(s).`));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.log(chalk_1.default.yellow('\n⚠️ No files needed resetting.'));
|
|
167
|
+
}
|
|
168
|
+
if (flags.verbose) {
|
|
169
|
+
this.log(`\n📊 Statistics:`);
|
|
170
|
+
this.log(` Files processed: ${result.processedFiles}`);
|
|
171
|
+
this.log(` Files with primaryKey: ${result.filesWithPrimaryKey}`);
|
|
172
|
+
this.log(` Files with sync: ${result.filesWithSync}`);
|
|
173
|
+
this.log(` Total primaryKeys removed: ${result.totalPrimaryKeys}`);
|
|
174
|
+
this.log(` Total syncs removed: ${result.totalSyncs}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
spinner.fail('File reset failed');
|
|
179
|
+
// Enhanced error logging
|
|
180
|
+
this.log('\n=== File Reset Error Details ===');
|
|
181
|
+
this.log(`Error type: ${error?.constructor?.name || 'Unknown'}`);
|
|
182
|
+
this.log(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
183
|
+
if (error instanceof Error && error.stack) {
|
|
184
|
+
this.log(`\nStack trace:`);
|
|
185
|
+
this.log(error.stack);
|
|
186
|
+
}
|
|
187
|
+
this.error(error);
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
// Reset singletons
|
|
191
|
+
(0, metadata_sync_1.resetSyncEngine)();
|
|
192
|
+
// Exit process
|
|
193
|
+
process.exit(0);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
exports.default = FileReset;
|
|
@@ -0,0 +1,101 @@
|
|
|
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 prompts_1 = require("@inquirer/prompts");
|
|
8
|
+
const ora_classic_1 = __importDefault(require("ora-classic"));
|
|
9
|
+
const metadata_sync_1 = require("@memberjunction/metadata-sync");
|
|
10
|
+
class Init extends core_1.Command {
|
|
11
|
+
static description = 'Initialize a directory for metadata synchronization';
|
|
12
|
+
static examples = [
|
|
13
|
+
`<%= config.bin %> <%= command.id %>`,
|
|
14
|
+
];
|
|
15
|
+
async run() {
|
|
16
|
+
const spinner = (0, ora_classic_1.default)();
|
|
17
|
+
try {
|
|
18
|
+
// Check if already initialized
|
|
19
|
+
const initService = new metadata_sync_1.InitService();
|
|
20
|
+
// Build options from user input
|
|
21
|
+
const options = {};
|
|
22
|
+
// Check for existing configuration
|
|
23
|
+
try {
|
|
24
|
+
// If .mj-sync.json exists, ask about overwrite
|
|
25
|
+
const overwrite = await (0, prompts_1.select)({
|
|
26
|
+
message: 'Directory already initialized. Overwrite configuration?',
|
|
27
|
+
choices: [
|
|
28
|
+
{ name: 'Yes', value: true },
|
|
29
|
+
{ name: 'No', value: false }
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
if (!overwrite) {
|
|
33
|
+
this.log('Initialization cancelled');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
options.overwrite = true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// File doesn't exist, proceed normally
|
|
40
|
+
}
|
|
41
|
+
// Ask if they want to set up an entity directory
|
|
42
|
+
const setupEntity = await (0, prompts_1.select)({
|
|
43
|
+
message: 'Would you like to set up an entity directory now?',
|
|
44
|
+
choices: [
|
|
45
|
+
{ name: 'Yes - AI Prompts', value: 'ai-prompts' },
|
|
46
|
+
{ name: 'Yes - Other entity', value: 'other' },
|
|
47
|
+
{ name: 'No - I\'ll set up later', value: 'no' }
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
options.setupEntity = setupEntity;
|
|
51
|
+
if (setupEntity === 'other') {
|
|
52
|
+
options.entityName = await (0, prompts_1.input)({
|
|
53
|
+
message: 'Enter the entity name (e.g., "Templates", "AI Models"):'
|
|
54
|
+
});
|
|
55
|
+
options.dirName = await (0, prompts_1.input)({
|
|
56
|
+
message: 'Enter the directory name:',
|
|
57
|
+
default: options.entityName.toLowerCase().replace(/\s+/g, '-')
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Initialize with callbacks
|
|
61
|
+
await initService.initialize(options, {
|
|
62
|
+
onProgress: (message) => {
|
|
63
|
+
spinner.start(message);
|
|
64
|
+
},
|
|
65
|
+
onSuccess: (message) => {
|
|
66
|
+
spinner.succeed(message);
|
|
67
|
+
},
|
|
68
|
+
onError: (message) => {
|
|
69
|
+
spinner.fail(message);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
this.log('\n✅ Initialization complete!');
|
|
73
|
+
// Show next steps
|
|
74
|
+
const nextSteps = initService.getNextSteps();
|
|
75
|
+
this.log('\nNext steps:');
|
|
76
|
+
nextSteps.forEach((step, index) => {
|
|
77
|
+
this.log(`${index + 1}. ${step.replace('mj-sync', 'mj sync')}`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
spinner.fail('Initialization failed');
|
|
82
|
+
// Enhanced error logging
|
|
83
|
+
this.log('\n=== Initialization Error Details ===');
|
|
84
|
+
this.log(`Error type: ${error?.constructor?.name || 'Unknown'}`);
|
|
85
|
+
this.log(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
86
|
+
if (error instanceof Error && error.stack) {
|
|
87
|
+
this.log(`\nStack trace:`);
|
|
88
|
+
this.log(error.stack);
|
|
89
|
+
}
|
|
90
|
+
// Check for error hints
|
|
91
|
+
if (error instanceof Error) {
|
|
92
|
+
const hint = new metadata_sync_1.InitService().getErrorHint(error);
|
|
93
|
+
if (hint) {
|
|
94
|
+
this.log(`\nHint: ${hint}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.error(error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.default = Init;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Pull extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
entity: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
filter: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
'multi-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
'no-validate': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private findEntityDirectories;
|
|
15
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const core_1 = require("@oclif/core");
|
|
30
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
31
|
+
const ora_classic_1 = __importDefault(require("ora-classic"));
|
|
32
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
33
|
+
const metadata_sync_1 = require("@memberjunction/metadata-sync");
|
|
34
|
+
class Pull extends core_1.Command {
|
|
35
|
+
static description = 'Pull metadata from database to local files';
|
|
36
|
+
static examples = [
|
|
37
|
+
`<%= config.bin %> <%= command.id %> --entity="AI Prompts"`,
|
|
38
|
+
`<%= config.bin %> <%= command.id %> --entity="AI Prompts" --filter="CategoryID='customer-service-id'"`,
|
|
39
|
+
];
|
|
40
|
+
static flags = {
|
|
41
|
+
entity: core_1.Flags.string({ description: 'Entity name to pull', required: true }),
|
|
42
|
+
filter: core_1.Flags.string({ description: 'Additional filter for pulling specific records' }),
|
|
43
|
+
'dry-run': core_1.Flags.boolean({ description: 'Show what would be pulled without actually pulling' }),
|
|
44
|
+
'multi-file': core_1.Flags.string({ description: 'Create a single file with multiple records (provide filename)' }),
|
|
45
|
+
verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed output' }),
|
|
46
|
+
'no-validate': core_1.Flags.boolean({ description: 'Skip validation before pull' }),
|
|
47
|
+
};
|
|
48
|
+
async run() {
|
|
49
|
+
const { flags } = await this.parse(Pull);
|
|
50
|
+
const spinner = (0, ora_classic_1.default)();
|
|
51
|
+
try {
|
|
52
|
+
// Load MJ config first
|
|
53
|
+
spinner.start('Loading configuration');
|
|
54
|
+
const mjConfig = (0, metadata_sync_1.loadMJConfig)();
|
|
55
|
+
if (!mjConfig) {
|
|
56
|
+
this.error('No mj.config.cjs found in current directory or parent directories');
|
|
57
|
+
}
|
|
58
|
+
// Stop spinner before provider initialization
|
|
59
|
+
spinner.stop();
|
|
60
|
+
// Initialize data provider
|
|
61
|
+
await (0, metadata_sync_1.initializeProvider)(mjConfig);
|
|
62
|
+
// Get singleton sync engine
|
|
63
|
+
const syncEngine = await (0, metadata_sync_1.getSyncEngine)((0, metadata_sync_1.getSystemUser)());
|
|
64
|
+
// Show success after initialization
|
|
65
|
+
if (flags.verbose) {
|
|
66
|
+
spinner.succeed('Configuration and metadata loaded');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
spinner.stop();
|
|
70
|
+
}
|
|
71
|
+
// Run validation unless disabled
|
|
72
|
+
if (!flags['no-validate']) {
|
|
73
|
+
spinner.start('Validating metadata...');
|
|
74
|
+
const validator = new metadata_sync_1.ValidationService({ verbose: flags.verbose });
|
|
75
|
+
const formatter = new metadata_sync_1.FormattingService();
|
|
76
|
+
const validationResult = await validator.validateDirectory(metadata_sync_1.configManager.getOriginalCwd());
|
|
77
|
+
spinner.stop();
|
|
78
|
+
if (!validationResult.isValid || validationResult.warnings.length > 0) {
|
|
79
|
+
// Show validation results
|
|
80
|
+
this.log('\n' + formatter.formatValidationResult(validationResult, flags.verbose));
|
|
81
|
+
if (!validationResult.isValid) {
|
|
82
|
+
// Ask for confirmation
|
|
83
|
+
const shouldContinue = await (0, prompts_1.select)({
|
|
84
|
+
message: 'Validation failed with errors. Do you want to continue anyway?',
|
|
85
|
+
choices: [
|
|
86
|
+
{ name: 'No, fix the errors first', value: false },
|
|
87
|
+
{ name: 'Yes, continue anyway', value: true }
|
|
88
|
+
],
|
|
89
|
+
default: false
|
|
90
|
+
});
|
|
91
|
+
if (!shouldContinue) {
|
|
92
|
+
this.error('Pull cancelled due to validation errors.');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.log(chalk_1.default.green('✓ Validation passed'));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Set target directory if specified via environment
|
|
101
|
+
const envTargetDir = process.env.METADATA_SYNC_TARGET_DIR;
|
|
102
|
+
// Create pull service and execute
|
|
103
|
+
const pullService = new metadata_sync_1.PullService(syncEngine, (0, metadata_sync_1.getSystemUser)());
|
|
104
|
+
try {
|
|
105
|
+
const result = await pullService.pull({
|
|
106
|
+
entity: flags.entity,
|
|
107
|
+
filter: flags.filter,
|
|
108
|
+
dryRun: flags['dry-run'],
|
|
109
|
+
multiFile: flags['multi-file'],
|
|
110
|
+
verbose: flags.verbose,
|
|
111
|
+
noValidate: flags['no-validate'],
|
|
112
|
+
targetDir: envTargetDir
|
|
113
|
+
}, {
|
|
114
|
+
onProgress: (message) => {
|
|
115
|
+
spinner.start(message);
|
|
116
|
+
},
|
|
117
|
+
onSuccess: (message) => {
|
|
118
|
+
spinner.succeed(message);
|
|
119
|
+
},
|
|
120
|
+
onError: (message) => {
|
|
121
|
+
spinner.fail(message);
|
|
122
|
+
},
|
|
123
|
+
onWarn: (message) => {
|
|
124
|
+
this.warn(message);
|
|
125
|
+
},
|
|
126
|
+
onLog: (message) => {
|
|
127
|
+
this.log(message);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (!flags['dry-run']) {
|
|
131
|
+
this.log(`\n✅ Pull completed successfully`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (pullError) {
|
|
135
|
+
// If it's a "multiple directories found" error, handle it specially
|
|
136
|
+
if (pullError instanceof Error && pullError.message.includes('Multiple directories found')) {
|
|
137
|
+
// Re-throw to be caught by outer handler
|
|
138
|
+
throw pullError;
|
|
139
|
+
}
|
|
140
|
+
throw pullError;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
spinner.fail('Pull failed');
|
|
145
|
+
// Handle multiple directories error
|
|
146
|
+
if (error instanceof Error && error.message.includes('Multiple directories found')) {
|
|
147
|
+
const entityDirs = await this.findEntityDirectories(flags.entity);
|
|
148
|
+
const targetDir = await (0, prompts_1.select)({
|
|
149
|
+
message: `Multiple directories found for entity "${flags.entity}". Which one to use?`,
|
|
150
|
+
choices: entityDirs.map(dir => ({ name: dir, value: dir }))
|
|
151
|
+
});
|
|
152
|
+
// Re-run with specific target directory
|
|
153
|
+
process.env.METADATA_SYNC_TARGET_DIR = targetDir;
|
|
154
|
+
await this.run();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Enhanced error logging
|
|
158
|
+
this.log('\n=== Pull Error Details ===');
|
|
159
|
+
this.log(`Error type: ${error?.constructor?.name || 'Unknown'}`);
|
|
160
|
+
this.log(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
161
|
+
if (error instanceof Error && error.stack) {
|
|
162
|
+
this.log(`\nStack trace:`);
|
|
163
|
+
this.log(error.stack);
|
|
164
|
+
}
|
|
165
|
+
// Context information
|
|
166
|
+
this.log(`\nContext:`);
|
|
167
|
+
this.log(`- Working directory: ${metadata_sync_1.configManager.getOriginalCwd()}`);
|
|
168
|
+
this.log(`- Entity: ${flags.entity || 'not specified'}`);
|
|
169
|
+
this.log(`- Filter: ${flags.filter || 'none'}`);
|
|
170
|
+
this.error(error);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
// Reset singletons
|
|
174
|
+
(0, metadata_sync_1.resetSyncEngine)();
|
|
175
|
+
// Exit process to prevent background MJ tasks from throwing errors
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async findEntityDirectories(entityName) {
|
|
180
|
+
// This is a simplified version - would need full implementation
|
|
181
|
+
const { findEntityDirectories } = await Promise.resolve().then(() => __importStar(require('@memberjunction/metadata-sync')));
|
|
182
|
+
return findEntityDirectories(metadata_sync_1.configManager.getOriginalCwd());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.default = Pull;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Push 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
|
+
'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
ci: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'no-validate': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|