@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
|
@@ -0,0 +1,183 @@
|
|
|
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 chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const metadata_sync_1 = require("@memberjunction/metadata-sync");
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
class Push extends core_1.Command {
|
|
13
|
+
static description = 'Push local file changes to the database';
|
|
14
|
+
static examples = [
|
|
15
|
+
`<%= config.bin %> <%= command.id %>`,
|
|
16
|
+
`<%= config.bin %> <%= command.id %> --dry-run`,
|
|
17
|
+
`<%= config.bin %> <%= command.id %> --dir="ai-prompts"`,
|
|
18
|
+
`<%= config.bin %> <%= command.id %> --ci`,
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
dir: core_1.Flags.string({ description: 'Specific entity directory to push' }),
|
|
22
|
+
'dry-run': core_1.Flags.boolean({ description: 'Show what would be pushed without actually pushing' }),
|
|
23
|
+
ci: core_1.Flags.boolean({ description: 'CI mode - no prompts, fail on issues' }),
|
|
24
|
+
verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed field-level output' }),
|
|
25
|
+
'no-validate': core_1.Flags.boolean({ description: 'Skip validation before push' }),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { flags } = await this.parse(Push);
|
|
29
|
+
const spinner = (0, ora_classic_1.default)();
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
try {
|
|
32
|
+
// Load configurations
|
|
33
|
+
spinner.start('Loading configuration');
|
|
34
|
+
const mjConfig = (0, metadata_sync_1.loadMJConfig)();
|
|
35
|
+
if (!mjConfig) {
|
|
36
|
+
this.error('No mj.config.cjs found in current directory or parent directories');
|
|
37
|
+
}
|
|
38
|
+
// Load sync config (currently only used internally by PushService)
|
|
39
|
+
const syncConfigDir = flags.dir ? path_1.default.resolve(metadata_sync_1.configManager.getOriginalCwd(), flags.dir) : metadata_sync_1.configManager.getOriginalCwd();
|
|
40
|
+
await (0, metadata_sync_1.loadSyncConfig)(syncConfigDir);
|
|
41
|
+
// Stop spinner before provider initialization
|
|
42
|
+
spinner.stop();
|
|
43
|
+
// Initialize data provider
|
|
44
|
+
await (0, metadata_sync_1.initializeProvider)(mjConfig);
|
|
45
|
+
// Initialize sync engine
|
|
46
|
+
const syncEngine = await (0, metadata_sync_1.getSyncEngine)((0, metadata_sync_1.getSystemUser)());
|
|
47
|
+
// Show success after initialization
|
|
48
|
+
if (flags.verbose) {
|
|
49
|
+
spinner.succeed('Configuration and metadata loaded');
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
spinner.stop();
|
|
53
|
+
}
|
|
54
|
+
// Run validation unless disabled
|
|
55
|
+
if (!flags['no-validate']) {
|
|
56
|
+
spinner.start('Validating metadata...');
|
|
57
|
+
const validator = new metadata_sync_1.ValidationService({ verbose: flags.verbose });
|
|
58
|
+
const formatter = new metadata_sync_1.FormattingService();
|
|
59
|
+
const targetDir = flags.dir ? path_1.default.resolve(metadata_sync_1.configManager.getOriginalCwd(), flags.dir) : metadata_sync_1.configManager.getOriginalCwd();
|
|
60
|
+
const validationResult = await validator.validateDirectory(targetDir);
|
|
61
|
+
spinner.stop();
|
|
62
|
+
if (!validationResult.isValid || validationResult.warnings.length > 0) {
|
|
63
|
+
// Show validation results
|
|
64
|
+
this.log('\n' + formatter.formatValidationResult(validationResult, flags.verbose));
|
|
65
|
+
if (!validationResult.isValid) {
|
|
66
|
+
// In CI mode, fail immediately
|
|
67
|
+
if (flags.ci) {
|
|
68
|
+
this.error('Validation failed. Cannot proceed with push.');
|
|
69
|
+
}
|
|
70
|
+
// Otherwise, ask for confirmation
|
|
71
|
+
const shouldContinue = await (0, prompts_1.confirm)({
|
|
72
|
+
message: 'Validation failed with errors. Do you want to continue anyway?',
|
|
73
|
+
default: false,
|
|
74
|
+
});
|
|
75
|
+
if (!shouldContinue) {
|
|
76
|
+
this.log(chalk_1.default.yellow('\n⚠️ Push cancelled due to validation errors.'));
|
|
77
|
+
// Exit cleanly without throwing an error
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.log(chalk_1.default.green('✓ Validation passed'));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Create push service and execute
|
|
87
|
+
const pushService = new metadata_sync_1.PushService(syncEngine, (0, metadata_sync_1.getSystemUser)());
|
|
88
|
+
const result = await pushService.push({
|
|
89
|
+
dir: flags.dir,
|
|
90
|
+
dryRun: flags['dry-run'],
|
|
91
|
+
verbose: flags.verbose,
|
|
92
|
+
noValidate: flags['no-validate'],
|
|
93
|
+
}, {
|
|
94
|
+
onProgress: (message) => {
|
|
95
|
+
spinner.start(message);
|
|
96
|
+
},
|
|
97
|
+
onSuccess: (message) => {
|
|
98
|
+
spinner.succeed(message);
|
|
99
|
+
},
|
|
100
|
+
onError: (message) => {
|
|
101
|
+
this.error(message);
|
|
102
|
+
},
|
|
103
|
+
onWarn: (message) => {
|
|
104
|
+
this.warn(message);
|
|
105
|
+
},
|
|
106
|
+
onLog: (message) => {
|
|
107
|
+
this.log(message);
|
|
108
|
+
},
|
|
109
|
+
onConfirm: async (message) => {
|
|
110
|
+
if (flags.ci) {
|
|
111
|
+
return true; // Always confirm in CI mode
|
|
112
|
+
}
|
|
113
|
+
return await (0, prompts_1.confirm)({ message });
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
// Show summary
|
|
117
|
+
const endTime = Date.now();
|
|
118
|
+
const formatter = new metadata_sync_1.FormattingService();
|
|
119
|
+
const summary = formatter.formatSyncSummary('push', {
|
|
120
|
+
created: result.created,
|
|
121
|
+
updated: result.updated,
|
|
122
|
+
unchanged: result.unchanged,
|
|
123
|
+
deleted: 0,
|
|
124
|
+
skipped: 0,
|
|
125
|
+
errors: result.errors,
|
|
126
|
+
duration: endTime - startTime,
|
|
127
|
+
});
|
|
128
|
+
this.log('\n' + summary);
|
|
129
|
+
// Handle result
|
|
130
|
+
if (result.errors > 0) {
|
|
131
|
+
if (flags.ci) {
|
|
132
|
+
this.error('Push failed with errors in CI mode');
|
|
133
|
+
}
|
|
134
|
+
const shouldContinue = await (0, prompts_1.confirm)({
|
|
135
|
+
message: 'Push completed with errors. Do you want to commit the successful changes?',
|
|
136
|
+
default: false,
|
|
137
|
+
});
|
|
138
|
+
if (!shouldContinue) {
|
|
139
|
+
throw new Error('Push cancelled due to errors');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Show warnings summary
|
|
143
|
+
if (result.warnings.length > 0) {
|
|
144
|
+
this.log(chalk_1.default.yellow(`\n⚠️ ${result.warnings.length} warning${result.warnings.length > 1 ? 's' : ''} during push`));
|
|
145
|
+
if (flags.verbose) {
|
|
146
|
+
result.warnings.forEach((warning) => this.log(` - ${warning}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Show final status
|
|
150
|
+
if (!flags['dry-run']) {
|
|
151
|
+
if (result.errors === 0) {
|
|
152
|
+
this.log(chalk_1.default.green('\n✅ Push completed successfully'));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
this.log(chalk_1.default.yellow('\n⚠️ Push completed with errors'));
|
|
156
|
+
}
|
|
157
|
+
if (result.sqlLogPath) {
|
|
158
|
+
this.log(`\n📄 SQL log saved to: ${path_1.default.relative(process.cwd(), result.sqlLogPath)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
spinner.fail('Push failed');
|
|
164
|
+
// Error handling - individual operations may have partially succeeded
|
|
165
|
+
// Enhanced error logging
|
|
166
|
+
this.log('\n=== Push Error Details ===');
|
|
167
|
+
this.log(`Error type: ${error?.constructor?.name || 'Unknown'}`);
|
|
168
|
+
this.log(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
169
|
+
if (error instanceof Error && error.stack) {
|
|
170
|
+
this.log(`\nStack trace:`);
|
|
171
|
+
this.log(error.stack);
|
|
172
|
+
}
|
|
173
|
+
this.error(error);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
// Reset singletons
|
|
177
|
+
(0, metadata_sync_1.resetSyncEngine)();
|
|
178
|
+
// Exit process
|
|
179
|
+
process.exit(0);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.default = Push;
|
|
@@ -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
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
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 ora_classic_1 = __importDefault(require("ora-classic"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const metadata_sync_1 = require("@memberjunction/metadata-sync");
|
|
10
|
+
class Status extends core_1.Command {
|
|
11
|
+
static description = 'Show status of local files vs database';
|
|
12
|
+
static examples = [
|
|
13
|
+
`<%= config.bin %> <%= command.id %>`,
|
|
14
|
+
`<%= config.bin %> <%= command.id %> --dir="ai-prompts"`,
|
|
15
|
+
`<%= config.bin %> <%= command.id %> --verbose`,
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
dir: core_1.Flags.string({ description: 'Specific entity directory to check status' }),
|
|
19
|
+
verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed field-level differences' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { flags } = await this.parse(Status);
|
|
23
|
+
const spinner = (0, ora_classic_1.default)();
|
|
24
|
+
try {
|
|
25
|
+
// Load configuration
|
|
26
|
+
spinner.start('Loading configuration');
|
|
27
|
+
const mjConfig = (0, metadata_sync_1.loadMJConfig)();
|
|
28
|
+
if (!mjConfig) {
|
|
29
|
+
this.error('No mj.config.cjs found in current directory or parent directories');
|
|
30
|
+
}
|
|
31
|
+
// Stop spinner before provider initialization
|
|
32
|
+
spinner.stop();
|
|
33
|
+
// Initialize data provider
|
|
34
|
+
await (0, metadata_sync_1.initializeProvider)(mjConfig);
|
|
35
|
+
// Initialize sync engine
|
|
36
|
+
const syncEngine = await (0, metadata_sync_1.getSyncEngine)((0, metadata_sync_1.getSystemUser)());
|
|
37
|
+
// Show success after initialization
|
|
38
|
+
if (flags.verbose) {
|
|
39
|
+
spinner.succeed('Configuration and metadata loaded');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
spinner.stop();
|
|
43
|
+
}
|
|
44
|
+
// Create status service and execute
|
|
45
|
+
const statusService = new metadata_sync_1.StatusService(syncEngine);
|
|
46
|
+
spinner.start('Checking status...');
|
|
47
|
+
const result = await statusService.checkStatus({
|
|
48
|
+
dir: flags.dir
|
|
49
|
+
}, {
|
|
50
|
+
onProgress: (message) => {
|
|
51
|
+
spinner.start(message);
|
|
52
|
+
},
|
|
53
|
+
onWarn: (message) => {
|
|
54
|
+
this.warn(message);
|
|
55
|
+
},
|
|
56
|
+
onLog: (message) => {
|
|
57
|
+
this.log(message);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
spinner.stop();
|
|
61
|
+
// Display results
|
|
62
|
+
// Show summary
|
|
63
|
+
const summary = result.summary;
|
|
64
|
+
const totalFiles = summary.new + summary.modified + summary.deleted + summary.unchanged;
|
|
65
|
+
if (totalFiles === 0) {
|
|
66
|
+
this.log(chalk_1.default.yellow('\nNo metadata files found in the specified directory.'));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
this.log(chalk_1.default.bold('\nSummary:'));
|
|
70
|
+
if (summary.new > 0) {
|
|
71
|
+
this.log(chalk_1.default.green(` ${summary.new} new file(s) to push`));
|
|
72
|
+
}
|
|
73
|
+
if (summary.modified > 0) {
|
|
74
|
+
this.log(chalk_1.default.yellow(` ${summary.modified} modified file(s) to push`));
|
|
75
|
+
}
|
|
76
|
+
if (summary.deleted > 0) {
|
|
77
|
+
this.log(chalk_1.default.red(` ${summary.deleted} deleted file(s) to remove`));
|
|
78
|
+
}
|
|
79
|
+
if (summary.unchanged > 0) {
|
|
80
|
+
this.log(chalk_1.default.gray(` ${summary.unchanged} unchanged file(s)`));
|
|
81
|
+
}
|
|
82
|
+
// Show details by entity if verbose
|
|
83
|
+
if (flags.verbose && result.details.length > 0) {
|
|
84
|
+
this.log(chalk_1.default.bold('\nDetails by entity:'));
|
|
85
|
+
for (const detail of result.details) {
|
|
86
|
+
this.log(`\n ${detail.entityName} (${detail.directory}):`);
|
|
87
|
+
this.log(` New: ${detail.new}, Modified: ${detail.modified}, Deleted: ${detail.deleted}, Unchanged: ${detail.unchanged}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
spinner.fail('Status check failed');
|
|
94
|
+
// Enhanced error logging
|
|
95
|
+
this.log('\n=== Status Error Details ===');
|
|
96
|
+
this.log(`Error type: ${error?.constructor?.name || 'Unknown'}`);
|
|
97
|
+
this.log(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
+
if (error instanceof Error && error.stack) {
|
|
99
|
+
this.log(`\nStack trace:`);
|
|
100
|
+
this.log(error.stack);
|
|
101
|
+
}
|
|
102
|
+
this.error(error);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
// Reset singletons
|
|
106
|
+
(0, metadata_sync_1.resetSyncEngine)();
|
|
107
|
+
// Exit process
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.default = Status;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Validate 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
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
'save-report': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
output: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
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 ora_classic_1 = __importDefault(require("ora-classic"));
|
|
31
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
32
|
+
const path = __importStar(require("path"));
|
|
33
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
34
|
+
const metadata_sync_1 = require("@memberjunction/metadata-sync");
|
|
35
|
+
class Validate extends core_1.Command {
|
|
36
|
+
static description = 'Validate metadata files';
|
|
37
|
+
static examples = [
|
|
38
|
+
`<%= config.bin %> <%= command.id %>`,
|
|
39
|
+
`<%= config.bin %> <%= command.id %> --dir="ai-prompts"`,
|
|
40
|
+
`<%= config.bin %> <%= command.id %> --save-report`,
|
|
41
|
+
`<%= config.bin %> <%= command.id %> --verbose`,
|
|
42
|
+
];
|
|
43
|
+
static flags = {
|
|
44
|
+
dir: core_1.Flags.string({ description: 'Specific entity directory to validate' }),
|
|
45
|
+
verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed validation output' }),
|
|
46
|
+
'save-report': core_1.Flags.boolean({ description: 'Save validation report as markdown file' }),
|
|
47
|
+
'output': core_1.Flags.string({ description: 'Output file path for validation report (default: validation-report.md)' }),
|
|
48
|
+
};
|
|
49
|
+
async run() {
|
|
50
|
+
const { flags } = await this.parse(Validate);
|
|
51
|
+
const spinner = (0, ora_classic_1.default)();
|
|
52
|
+
try {
|
|
53
|
+
// Load configuration
|
|
54
|
+
spinner.start('Loading configuration');
|
|
55
|
+
const mjConfig = (0, metadata_sync_1.loadMJConfig)();
|
|
56
|
+
if (!mjConfig) {
|
|
57
|
+
this.error('No mj.config.cjs found in current directory or parent directories');
|
|
58
|
+
}
|
|
59
|
+
// Stop spinner before provider initialization
|
|
60
|
+
spinner.stop();
|
|
61
|
+
// Initialize data provider
|
|
62
|
+
await (0, metadata_sync_1.initializeProvider)(mjConfig);
|
|
63
|
+
// Initialize sync engine (needed for metadata access)
|
|
64
|
+
await (0, metadata_sync_1.getSyncEngine)((0, metadata_sync_1.getSystemUser)());
|
|
65
|
+
// Show success after initialization
|
|
66
|
+
if (flags.verbose) {
|
|
67
|
+
spinner.succeed('Configuration and metadata loaded');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
spinner.stop();
|
|
71
|
+
}
|
|
72
|
+
// Create validation service
|
|
73
|
+
const validator = new metadata_sync_1.ValidationService({ verbose: flags.verbose });
|
|
74
|
+
const formatter = new metadata_sync_1.FormattingService();
|
|
75
|
+
// Determine directory to validate
|
|
76
|
+
const targetDir = flags.dir
|
|
77
|
+
? path.resolve(metadata_sync_1.configManager.getOriginalCwd(), flags.dir)
|
|
78
|
+
: metadata_sync_1.configManager.getOriginalCwd();
|
|
79
|
+
spinner.start(`Validating metadata in ${path.relative(process.cwd(), targetDir)}...`);
|
|
80
|
+
// Run validation
|
|
81
|
+
const validationResult = await validator.validateDirectory(targetDir);
|
|
82
|
+
spinner.stop();
|
|
83
|
+
// Format and display results
|
|
84
|
+
const formattedResult = formatter.formatValidationResult(validationResult, flags.verbose);
|
|
85
|
+
this.log('\n' + formattedResult);
|
|
86
|
+
// Save report if requested
|
|
87
|
+
if (flags['save-report']) {
|
|
88
|
+
const reportPath = flags.output || 'validation-report.md';
|
|
89
|
+
const fullReportPath = path.resolve(reportPath);
|
|
90
|
+
// Generate markdown report
|
|
91
|
+
const report = formatter.formatValidationResultAsMarkdown(validationResult);
|
|
92
|
+
await fs_extra_1.default.writeFile(fullReportPath, report, 'utf8');
|
|
93
|
+
this.log(chalk_1.default.green(`\n✅ Validation report saved to: ${path.relative(process.cwd(), fullReportPath)}`));
|
|
94
|
+
}
|
|
95
|
+
// Show summary
|
|
96
|
+
const totalIssues = validationResult.errors.length + validationResult.warnings.length;
|
|
97
|
+
if (validationResult.isValid && validationResult.warnings.length === 0) {
|
|
98
|
+
this.log(chalk_1.default.green('\n✅ All metadata files are valid!'));
|
|
99
|
+
}
|
|
100
|
+
else if (validationResult.isValid && validationResult.warnings.length > 0) {
|
|
101
|
+
this.log(chalk_1.default.yellow(`\n⚠️ Validation passed with ${validationResult.warnings.length} warning(s)`));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.log(chalk_1.default.red(`\n❌ Validation failed with ${validationResult.errors.length} error(s) and ${validationResult.warnings.length} warning(s)`));
|
|
105
|
+
}
|
|
106
|
+
// Exit with error code if validation failed
|
|
107
|
+
if (!validationResult.isValid) {
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
spinner.fail('Validation failed');
|
|
113
|
+
// Enhanced error logging
|
|
114
|
+
this.log('\n=== Validation Error Details ===');
|
|
115
|
+
this.log(`Error type: ${error?.constructor?.name || 'Unknown'}`);
|
|
116
|
+
this.log(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
+
if (error instanceof Error && error.stack) {
|
|
118
|
+
this.log(`\nStack trace:`);
|
|
119
|
+
this.log(error.stack);
|
|
120
|
+
}
|
|
121
|
+
this.error(error);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
// Reset singletons
|
|
125
|
+
(0, metadata_sync_1.resetSyncEngine)();
|
|
126
|
+
// Exit process
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.default = Validate;
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
debounce: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
'no-validate': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
private watchController?;
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
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 ora_classic_1 = __importDefault(require("ora-classic"));
|
|
31
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
32
|
+
const path = __importStar(require("path"));
|
|
33
|
+
const metadata_sync_1 = require("@memberjunction/metadata-sync");
|
|
34
|
+
class Watch extends core_1.Command {
|
|
35
|
+
static description = 'Watch for file changes and sync automatically';
|
|
36
|
+
static examples = [
|
|
37
|
+
`<%= config.bin %> <%= command.id %>`,
|
|
38
|
+
`<%= config.bin %> <%= command.id %> --dir="ai-prompts"`,
|
|
39
|
+
`<%= config.bin %> <%= command.id %> --debounce=1000`,
|
|
40
|
+
`<%= config.bin %> <%= command.id %> --no-validate`,
|
|
41
|
+
];
|
|
42
|
+
static flags = {
|
|
43
|
+
dir: core_1.Flags.string({ description: 'Specific entity directory to watch' }),
|
|
44
|
+
debounce: core_1.Flags.integer({ description: 'Debounce delay in milliseconds (default: 500)' }),
|
|
45
|
+
'no-validate': core_1.Flags.boolean({ description: 'Skip validation before sync' }),
|
|
46
|
+
verbose: core_1.Flags.boolean({ char: 'v', description: 'Show detailed output' }),
|
|
47
|
+
};
|
|
48
|
+
watchController;
|
|
49
|
+
async run() {
|
|
50
|
+
const { flags } = await this.parse(Watch);
|
|
51
|
+
const spinner = (0, ora_classic_1.default)();
|
|
52
|
+
try {
|
|
53
|
+
// Load configuration
|
|
54
|
+
spinner.start('Loading configuration');
|
|
55
|
+
const mjConfig = (0, metadata_sync_1.loadMJConfig)();
|
|
56
|
+
if (!mjConfig) {
|
|
57
|
+
this.error('No mj.config.cjs found in current directory or parent directories');
|
|
58
|
+
}
|
|
59
|
+
// Load sync config
|
|
60
|
+
const syncConfigDir = flags.dir ? path.resolve(metadata_sync_1.configManager.getOriginalCwd(), flags.dir) : metadata_sync_1.configManager.getOriginalCwd();
|
|
61
|
+
const syncConfig = await (0, metadata_sync_1.loadSyncConfig)(syncConfigDir);
|
|
62
|
+
// Stop spinner before provider initialization
|
|
63
|
+
spinner.stop();
|
|
64
|
+
// Initialize data provider
|
|
65
|
+
await (0, metadata_sync_1.initializeProvider)(mjConfig);
|
|
66
|
+
// Initialize sync engine
|
|
67
|
+
const syncEngine = await (0, metadata_sync_1.getSyncEngine)((0, metadata_sync_1.getSystemUser)());
|
|
68
|
+
// Show success after initialization
|
|
69
|
+
spinner.succeed('Configuration and metadata loaded');
|
|
70
|
+
// Determine watch directory
|
|
71
|
+
const watchDir = flags.dir
|
|
72
|
+
? path.resolve(metadata_sync_1.configManager.getOriginalCwd(), flags.dir)
|
|
73
|
+
: metadata_sync_1.configManager.getOriginalCwd();
|
|
74
|
+
// Create watch service
|
|
75
|
+
const watchService = new metadata_sync_1.WatchService(syncEngine);
|
|
76
|
+
// Setup graceful shutdown
|
|
77
|
+
process.on('SIGINT', async () => {
|
|
78
|
+
this.log(chalk_1.default.yellow('\n\n⏹️ Stopping file watcher...'));
|
|
79
|
+
if (this.watchController) {
|
|
80
|
+
await this.watchController.stop();
|
|
81
|
+
}
|
|
82
|
+
(0, metadata_sync_1.resetSyncEngine)();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
});
|
|
85
|
+
process.on('SIGTERM', async () => {
|
|
86
|
+
if (this.watchController) {
|
|
87
|
+
await this.watchController.stop();
|
|
88
|
+
}
|
|
89
|
+
(0, metadata_sync_1.resetSyncEngine)();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
// Start watching
|
|
93
|
+
this.log(chalk_1.default.bold(`\n👀 Watching for changes in: ${path.relative(process.cwd(), watchDir)}`));
|
|
94
|
+
this.log(chalk_1.default.gray('Press Ctrl+C to stop watching\n'));
|
|
95
|
+
this.watchController = await watchService.watch({
|
|
96
|
+
dir: flags.dir,
|
|
97
|
+
debounceMs: flags.debounce || 500
|
|
98
|
+
}, {
|
|
99
|
+
onFileAdd: (filePath, entityDir, entityConfig) => {
|
|
100
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
101
|
+
this.log(chalk_1.default.gray(`➕ added: ${relativePath}`));
|
|
102
|
+
},
|
|
103
|
+
onFileChange: (filePath, entityDir, entityConfig) => {
|
|
104
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
105
|
+
this.log(chalk_1.default.gray(`📝 changed: ${relativePath}`));
|
|
106
|
+
},
|
|
107
|
+
onFileDelete: (filePath, entityDir, entityConfig) => {
|
|
108
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
109
|
+
this.log(chalk_1.default.gray(`❌ deleted: ${relativePath}`));
|
|
110
|
+
},
|
|
111
|
+
onRecordSaved: (entity, isNew, entityConfig) => {
|
|
112
|
+
const action = isNew ? 'created' : 'updated';
|
|
113
|
+
this.log(chalk_1.default.green(`✅ Record ${action} for ${entityConfig.entity}`));
|
|
114
|
+
},
|
|
115
|
+
onError: (error) => {
|
|
116
|
+
spinner.fail('Watch error');
|
|
117
|
+
this.error(error);
|
|
118
|
+
},
|
|
119
|
+
onWarn: (message) => {
|
|
120
|
+
this.warn(message);
|
|
121
|
+
},
|
|
122
|
+
onLog: (message) => {
|
|
123
|
+
if (flags.verbose) {
|
|
124
|
+
this.log(message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// Keep process alive
|
|
129
|
+
await new Promise(() => {
|
|
130
|
+
// This promise never resolves, keeping the process running
|
|
131
|
+
// until interrupted by SIGINT/SIGTERM
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
spinner.fail('Watch failed');
|
|
136
|
+
// Enhanced error logging
|
|
137
|
+
this.log('\n=== Watch Error Details ===');
|
|
138
|
+
this.log(`Error type: ${error?.constructor?.name || 'Unknown'}`);
|
|
139
|
+
this.log(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
if (error instanceof Error && error.stack) {
|
|
141
|
+
this.log(`\nStack trace:`);
|
|
142
|
+
this.log(error.stack);
|
|
143
|
+
}
|
|
144
|
+
this.error(error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.default = Watch;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Init hook runs once when the CLI starts up.
|
|
5
|
+
* Currently unused but required by oclif configuration.
|
|
6
|
+
*/
|
|
7
|
+
const hook = async function () {
|
|
8
|
+
// No initialization needed at this time
|
|
9
|
+
};
|
|
10
|
+
exports.default = hook;
|