@memberjunction/cli 5.4.0 → 5.5.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/doctor/index.d.ts +12 -0
- package/dist/commands/doctor/index.d.ts.map +1 -0
- package/dist/commands/doctor/index.js +69 -0
- package/dist/commands/doctor/index.js.map +1 -0
- package/dist/commands/install/index.d.ts +18 -136
- package/dist/commands/install/index.d.ts.map +1 -1
- package/dist/commands/install/index.js +189 -390
- package/dist/commands/install/index.js.map +1 -1
- package/dist/commands/sql-audit/index.d.ts +25 -0
- package/dist/commands/sql-audit/index.d.ts.map +1 -0
- package/dist/commands/sql-audit/index.js +198 -0
- package/dist/commands/sql-audit/index.js.map +1 -0
- package/dist/commands/sql-convert/index.d.ts +30 -0
- package/dist/commands/sql-convert/index.d.ts.map +1 -0
- package/dist/commands/sql-convert/index.js +128 -0
- package/dist/commands/sql-convert/index.js.map +1 -0
- package/dist/commands/translate-sql/index.d.ts +39 -0
- package/dist/commands/translate-sql/index.d.ts.map +1 -0
- package/dist/commands/translate-sql/index.js +229 -0
- package/dist/commands/translate-sql/index.js.map +1 -0
- package/dist/lib/legacy-install.d.ts +29 -0
- package/dist/lib/legacy-install.d.ts.map +1 -0
- package/dist/lib/legacy-install.js +391 -0
- package/dist/lib/legacy-install.js.map +1 -0
- package/dist/light-commands.d.ts.map +1 -1
- package/dist/light-commands.js +6 -1
- package/dist/light-commands.js.map +1 -1
- package/dist/translate-sql/classifier.d.ts +27 -0
- package/dist/translate-sql/classifier.d.ts.map +1 -0
- package/dist/translate-sql/classifier.js +118 -0
- package/dist/translate-sql/classifier.js.map +1 -0
- package/dist/translate-sql/groundTruth.d.ts +25 -0
- package/dist/translate-sql/groundTruth.d.ts.map +1 -0
- package/dist/translate-sql/groundTruth.js +93 -0
- package/dist/translate-sql/groundTruth.js.map +1 -0
- package/dist/translate-sql/index.d.ts +5 -0
- package/dist/translate-sql/index.d.ts.map +1 -0
- package/dist/translate-sql/index.js +5 -0
- package/dist/translate-sql/index.js.map +1 -0
- package/dist/translate-sql/reportGenerator.d.ts +26 -0
- package/dist/translate-sql/reportGenerator.d.ts.map +1 -0
- package/dist/translate-sql/reportGenerator.js +83 -0
- package/dist/translate-sql/reportGenerator.js.map +1 -0
- package/dist/translate-sql/ruleTranslator.d.ts +21 -0
- package/dist/translate-sql/ruleTranslator.d.ts.map +1 -0
- package/dist/translate-sql/ruleTranslator.js +74 -0
- package/dist/translate-sql/ruleTranslator.js.map +1 -0
- package/oclif.manifest.json +522 -158
- package/package.json +14 -12
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { ClassifySQLBatch, RuleBasedTranslate, GenerateTranslationReport, BuildGroundTruthPromptSection, } from '../../translate-sql/index.js';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
/**
|
|
6
|
+
* CLI command for translating SQL between database dialects.
|
|
7
|
+
* Scans metadata (Queries, UserViews, RLS filters) or explicit SQL fragments,
|
|
8
|
+
* classifies them, applies rule-based or LLM translation, and generates a report.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* mj translate-sql --from sqlserver --to postgresql --scope queries
|
|
12
|
+
* mj translate-sql --from sqlserver --to postgresql --scope all --dry-run
|
|
13
|
+
* mj translate-sql --from sqlserver --to postgresql --sql "SELECT TOP 10 [Name] FROM [Users]"
|
|
14
|
+
*/
|
|
15
|
+
export default class TranslateSQL extends Command {
|
|
16
|
+
static { this.description = 'Translate SQL fragments between database dialects (SQL Server ↔ PostgreSQL)'; }
|
|
17
|
+
static { this.examples = [
|
|
18
|
+
'<%= config.bin %> translate-sql --from sqlserver --to postgresql --dry-run',
|
|
19
|
+
'<%= config.bin %> translate-sql --from sqlserver --to postgresql --scope queries',
|
|
20
|
+
'<%= config.bin %> translate-sql --from sqlserver --to postgresql --sql "SELECT TOP 10 [Name] FROM [Users]"',
|
|
21
|
+
'<%= config.bin %> translate-sql --from sqlserver --to postgresql --scope all --output ./reports',
|
|
22
|
+
]; }
|
|
23
|
+
static { this.flags = {
|
|
24
|
+
from: Flags.string({
|
|
25
|
+
description: 'Source SQL dialect',
|
|
26
|
+
required: true,
|
|
27
|
+
options: ['sqlserver', 'postgresql'],
|
|
28
|
+
}),
|
|
29
|
+
to: Flags.string({
|
|
30
|
+
description: 'Target SQL dialect',
|
|
31
|
+
required: true,
|
|
32
|
+
options: ['sqlserver', 'postgresql'],
|
|
33
|
+
}),
|
|
34
|
+
scope: Flags.string({
|
|
35
|
+
description: 'What to scan for translation',
|
|
36
|
+
options: ['queries', 'views', 'filters', 'all'],
|
|
37
|
+
default: 'all',
|
|
38
|
+
}),
|
|
39
|
+
sql: Flags.string({
|
|
40
|
+
description: 'Translate a single SQL fragment (inline mode)',
|
|
41
|
+
}),
|
|
42
|
+
'dry-run': Flags.boolean({
|
|
43
|
+
description: 'Show what would be translated without making changes',
|
|
44
|
+
default: false,
|
|
45
|
+
}),
|
|
46
|
+
review: Flags.boolean({
|
|
47
|
+
description: 'Generate review report only (no writes to database)',
|
|
48
|
+
default: false,
|
|
49
|
+
}),
|
|
50
|
+
output: Flags.string({
|
|
51
|
+
char: 'o',
|
|
52
|
+
description: 'Output directory for the translation report',
|
|
53
|
+
}),
|
|
54
|
+
force: Flags.boolean({
|
|
55
|
+
description: 'Re-translate even if platform variants already exist',
|
|
56
|
+
default: false,
|
|
57
|
+
}),
|
|
58
|
+
}; }
|
|
59
|
+
async run() {
|
|
60
|
+
const { flags } = await this.parse(TranslateSQL);
|
|
61
|
+
const from = flags.from;
|
|
62
|
+
const to = flags.to;
|
|
63
|
+
if (from === to) {
|
|
64
|
+
this.error('Source and target dialects must be different');
|
|
65
|
+
}
|
|
66
|
+
// Inline single-SQL mode
|
|
67
|
+
if (flags.sql) {
|
|
68
|
+
await this.translateInline(flags.sql, from, to);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Batch mode: scan metadata
|
|
72
|
+
await this.translateBatch(from, to, flags);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Translates a single SQL fragment provided via --sql flag.
|
|
76
|
+
*/
|
|
77
|
+
async translateInline(sql, from, to) {
|
|
78
|
+
const [result] = ClassifySQLBatch([sql], from);
|
|
79
|
+
this.log(`\nClassification: ${result.classification}`);
|
|
80
|
+
if (result.markers.length > 0) {
|
|
81
|
+
this.log(`Dialect markers: ${result.markers.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
if (result.classification === 'standard') {
|
|
84
|
+
this.log(`\nSQL is standard — no translation needed.`);
|
|
85
|
+
this.log(sql);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (result.classification === 'rule-based') {
|
|
89
|
+
const translated = RuleBasedTranslate(sql, from, to);
|
|
90
|
+
this.log(`\nRule-based translation (${translated.appliedRules.join(', ')}):`);
|
|
91
|
+
this.log(translated.translatedSQL);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// LLM-needed
|
|
95
|
+
this.log(`\nThis SQL requires LLM translation.`);
|
|
96
|
+
this.log(`Markers: ${result.markers.join(', ')}`);
|
|
97
|
+
this.log(`\nGround truth examples for LLM prompt:`);
|
|
98
|
+
this.log(BuildGroundTruthPromptSection(from, to));
|
|
99
|
+
this.log(`\nOriginal SQL:`);
|
|
100
|
+
this.log(sql);
|
|
101
|
+
this.log(`\nNote: LLM integration requires AI provider configuration.`);
|
|
102
|
+
this.log(`Use the review report to see all fragments needing LLM translation.`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Scans metadata for SQL fragments and generates a translation report.
|
|
106
|
+
*/
|
|
107
|
+
async translateBatch(from, to, flags) {
|
|
108
|
+
this.log(`\nScanning for SQL fragments to translate (${from} → ${to})...`);
|
|
109
|
+
this.log(`Scope: ${flags.scope}`);
|
|
110
|
+
// For now, we work with metadata loaded via the MJ bootstrap.
|
|
111
|
+
// The prerun hook loads the bootstrap for heavy commands.
|
|
112
|
+
const items = await this.collectSQLFragments(from, flags.scope);
|
|
113
|
+
if (items.length === 0) {
|
|
114
|
+
this.log('\nNo SQL fragments found to translate.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Classify all fragments
|
|
118
|
+
const classified = ClassifySQLBatch(items.map(i => i.sql), from);
|
|
119
|
+
// Translate
|
|
120
|
+
const reportItems = [];
|
|
121
|
+
for (let i = 0; i < items.length; i++) {
|
|
122
|
+
const item = items[i];
|
|
123
|
+
const classResult = classified[i];
|
|
124
|
+
if (classResult.classification === 'standard') {
|
|
125
|
+
reportItems.push({
|
|
126
|
+
source: item.label,
|
|
127
|
+
originalSQL: item.sql,
|
|
128
|
+
classification: 'standard',
|
|
129
|
+
translatedSQL: null,
|
|
130
|
+
method: 'skipped',
|
|
131
|
+
markers: classResult.markers,
|
|
132
|
+
});
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (classResult.classification === 'rule-based') {
|
|
136
|
+
const translated = RuleBasedTranslate(item.sql, from, to);
|
|
137
|
+
reportItems.push({
|
|
138
|
+
source: item.label,
|
|
139
|
+
originalSQL: item.sql,
|
|
140
|
+
classification: 'rule-based',
|
|
141
|
+
translatedSQL: translated.translatedSQL,
|
|
142
|
+
method: 'rule-based',
|
|
143
|
+
markers: classResult.markers,
|
|
144
|
+
note: `Rules: ${translated.appliedRules.join(', ')}`,
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// LLM-needed — flag for review
|
|
149
|
+
reportItems.push({
|
|
150
|
+
source: item.label,
|
|
151
|
+
originalSQL: item.sql,
|
|
152
|
+
classification: 'llm-needed',
|
|
153
|
+
translatedSQL: null,
|
|
154
|
+
method: 'flagged',
|
|
155
|
+
markers: classResult.markers,
|
|
156
|
+
note: 'Requires LLM translation — configure AI provider and re-run.',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Print summary
|
|
160
|
+
const standard = reportItems.filter(i => i.classification === 'standard').length;
|
|
161
|
+
const ruleBased = reportItems.filter(i => i.method === 'rule-based').length;
|
|
162
|
+
const flagged = reportItems.filter(i => i.method === 'flagged').length;
|
|
163
|
+
this.log(`\nResults:`);
|
|
164
|
+
this.log(` Total fragments: ${reportItems.length}`);
|
|
165
|
+
this.log(` Standard SQL (no translation): ${standard}`);
|
|
166
|
+
this.log(` Rule-based translations: ${ruleBased}`);
|
|
167
|
+
this.log(` Flagged for LLM/review: ${flagged}`);
|
|
168
|
+
// Generate report
|
|
169
|
+
const report = GenerateTranslationReport(reportItems, from, to);
|
|
170
|
+
if (flags.output) {
|
|
171
|
+
const outputDir = flags.output;
|
|
172
|
+
if (!fs.existsSync(outputDir)) {
|
|
173
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
const reportPath = path.join(outputDir, `translation-report-${from}-to-${to}.md`);
|
|
176
|
+
fs.writeFileSync(reportPath, report, 'utf-8');
|
|
177
|
+
this.log(`\nReport written to: ${reportPath}`);
|
|
178
|
+
}
|
|
179
|
+
if (flags['dry-run'] || flags.review) {
|
|
180
|
+
this.log(`\nDry run / review mode — no changes written to database.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Collects SQL fragments from loaded metadata.
|
|
185
|
+
*/
|
|
186
|
+
async collectSQLFragments(from, scope) {
|
|
187
|
+
const fragments = [];
|
|
188
|
+
try {
|
|
189
|
+
const { Metadata } = await import('@memberjunction/core');
|
|
190
|
+
const md = new Metadata();
|
|
191
|
+
// Queries
|
|
192
|
+
if (scope === 'all' || scope === 'queries') {
|
|
193
|
+
for (const query of md.Queries) {
|
|
194
|
+
if (query.SQL) {
|
|
195
|
+
fragments.push({ label: `Query: ${query.Name}`, sql: query.SQL });
|
|
196
|
+
}
|
|
197
|
+
if (query.CacheValidationSQL) {
|
|
198
|
+
fragments.push({
|
|
199
|
+
label: `Query CacheValidation: ${query.Name}`,
|
|
200
|
+
sql: query.CacheValidationSQL
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Row Level Security Filters (accessed via Provider, not exposed on Metadata directly)
|
|
206
|
+
if (scope === 'all' || scope === 'filters') {
|
|
207
|
+
for (const filter of Metadata.Provider.RowLevelSecurityFilters) {
|
|
208
|
+
if (filter.FilterText) {
|
|
209
|
+
fragments.push({
|
|
210
|
+
label: `RLS Filter: ${filter.Name}`,
|
|
211
|
+
sql: filter.FilterText
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// UserViews - accessed through RunView since they're entity data
|
|
217
|
+
// In a full implementation, we'd query the UserView entity
|
|
218
|
+
// For now, we note that view SQL is mainly handled through ExtraFilter/OrderBy
|
|
219
|
+
// which already support PlatformSQL
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
223
|
+
this.warn(`Could not load metadata: ${message}`);
|
|
224
|
+
this.warn('Run this command in a configured MemberJunction environment.');
|
|
225
|
+
}
|
|
226
|
+
return fragments;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/translate-sql/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy interactive installer (pre-engine).
|
|
3
|
+
*
|
|
4
|
+
* Preserves the original `mj install` behavior from v3.x for the
|
|
5
|
+
* ZIP-only distribution workflow. Access via `mj install --legacy`.
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from '@oclif/core';
|
|
8
|
+
export declare class LegacyInstaller {
|
|
9
|
+
private cmd;
|
|
10
|
+
private verbose;
|
|
11
|
+
constructor(cmd: Command, verbose: boolean);
|
|
12
|
+
Run(): Promise<void>;
|
|
13
|
+
private bootstrapGeneratedEntities;
|
|
14
|
+
private bootstrapMjapi;
|
|
15
|
+
private processMjExplorer;
|
|
16
|
+
private checkNodeVersion;
|
|
17
|
+
private checkAvailableDiskSpace;
|
|
18
|
+
private verifyDirs;
|
|
19
|
+
private getUserConfiguration;
|
|
20
|
+
private promptForConfiguration;
|
|
21
|
+
private promptForAuthConfiguration;
|
|
22
|
+
private promptForUserSetup;
|
|
23
|
+
private promptForAiKeys;
|
|
24
|
+
private writeEnvFile;
|
|
25
|
+
private renameFolderToMjBase;
|
|
26
|
+
private updateEnvironmentFiles;
|
|
27
|
+
UpdateConfigNewUserSetup(userName?: string, firstName?: string, lastName?: string, email?: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=legacy-install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-install.d.ts","sourceRoot":"","sources":["../../src/lib/legacy-install.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAiD3C,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,OAAO,CAAU;gBAEb,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IAKpC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB1B,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,cAAc;YAYR,iBAAiB;IAwB/B,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,uBAAuB;IA8B/B,OAAO,CAAC,UAAU;YAeJ,oBAAoB;YA0BpB,sBAAsB;YA4DtB,0BAA0B;YAkB1B,kBAAkB;YAkBlB,eAAe;IAY7B,OAAO,CAAC,YAAY;IAkDpB,OAAO,CAAC,oBAAoB;YAgBd,sBAAsB;IA4B9B,wBAAwB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuCxH"}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy interactive installer (pre-engine).
|
|
3
|
+
*
|
|
4
|
+
* Preserves the original `mj install` behavior from v3.x for the
|
|
5
|
+
* ZIP-only distribution workflow. Access via `mj install --legacy`.
|
|
6
|
+
*/
|
|
7
|
+
import { confirm, input, select } from '@inquirer/prompts';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
import recast from 'recast';
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { ZodError, z } from 'zod';
|
|
15
|
+
// Directories are relative to execution cwd
|
|
16
|
+
const GENERATED_ENTITIES_DIR = 'GeneratedEntities';
|
|
17
|
+
const SQL_SCRIPTS_DIR = 'SQL Scripts';
|
|
18
|
+
const GENERATED_DIR = 'generated';
|
|
19
|
+
const MJ_BASE_DIR = 'MJ_BASE';
|
|
20
|
+
const MJAPI_DIR = 'MJAPI';
|
|
21
|
+
const MJEXPLORER_DIR = 'MJExplorer';
|
|
22
|
+
const configSchema = z.object({
|
|
23
|
+
dbUrl: z.string().min(1),
|
|
24
|
+
dbInstance: z.string(),
|
|
25
|
+
dbTrustServerCertificate: z.coerce
|
|
26
|
+
.boolean()
|
|
27
|
+
.default(false)
|
|
28
|
+
.transform((v) => (v ? 'Y' : 'N')),
|
|
29
|
+
dbDatabase: z.string().min(1),
|
|
30
|
+
dbPort: z.number({ coerce: true }).int().positive(),
|
|
31
|
+
codeGenLogin: z.string(),
|
|
32
|
+
codeGenPwD: z.string(),
|
|
33
|
+
mjAPILogin: z.string(),
|
|
34
|
+
mjAPIPwD: z.string(),
|
|
35
|
+
graphQLPort: z.number({ coerce: true }).int().positive().optional(),
|
|
36
|
+
authType: z.enum(['MSAL', 'AUTH0', 'BOTH']),
|
|
37
|
+
msalWebClientId: z.string().optional(),
|
|
38
|
+
msalTenantId: z.string().optional(),
|
|
39
|
+
auth0ClientId: z.string().optional(),
|
|
40
|
+
auth0ClientSecret: z.string().optional(),
|
|
41
|
+
auth0Domain: z.string().optional(),
|
|
42
|
+
createNewUser: z.coerce.boolean().optional(),
|
|
43
|
+
userEmail: z.string().email().or(z.literal('')).optional().default(''),
|
|
44
|
+
userFirstName: z.string().optional(),
|
|
45
|
+
userLastName: z.string().optional(),
|
|
46
|
+
userName: z.string().optional(),
|
|
47
|
+
openAIAPIKey: z.string().optional(),
|
|
48
|
+
anthropicAPIKey: z.string().optional(),
|
|
49
|
+
mistralAPIKey: z.string().optional(),
|
|
50
|
+
});
|
|
51
|
+
export class LegacyInstaller {
|
|
52
|
+
constructor(cmd, verbose) {
|
|
53
|
+
this.cmd = cmd;
|
|
54
|
+
this.verbose = verbose;
|
|
55
|
+
}
|
|
56
|
+
async Run() {
|
|
57
|
+
this.checkNodeVersion();
|
|
58
|
+
this.checkAvailableDiskSpace(2);
|
|
59
|
+
this.verifyDirs(GENERATED_ENTITIES_DIR, SQL_SCRIPTS_DIR, MJAPI_DIR, MJEXPLORER_DIR);
|
|
60
|
+
const userConfig = await this.getUserConfiguration();
|
|
61
|
+
this.cmd.log('Setting up MemberJunction Distribution...');
|
|
62
|
+
if (this.verbose) {
|
|
63
|
+
this.cmd.log(JSON.stringify(userConfig, null, 2));
|
|
64
|
+
}
|
|
65
|
+
this.bootstrapGeneratedEntities();
|
|
66
|
+
this.writeEnvFile(userConfig);
|
|
67
|
+
this.bootstrapMjapi(userConfig);
|
|
68
|
+
await this.processMjExplorer(userConfig);
|
|
69
|
+
this.cmd.log('Installation complete!');
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Top-level steps
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
bootstrapGeneratedEntities() {
|
|
75
|
+
this.cmd.log('\nBootstrapping GeneratedEntities...');
|
|
76
|
+
this.cmd.log('Running npm install...');
|
|
77
|
+
execSync('npm install', { stdio: 'inherit', cwd: GENERATED_ENTITIES_DIR });
|
|
78
|
+
}
|
|
79
|
+
bootstrapMjapi(userConfig) {
|
|
80
|
+
this.cmd.log('\n\nBootstrapping MJAPI...');
|
|
81
|
+
this.cmd.log(' Running npm link for generated code...');
|
|
82
|
+
execSync('npm link ../GeneratedEntities ../GeneratedActions', { stdio: 'inherit', cwd: MJAPI_DIR });
|
|
83
|
+
this.cmd.log('Running CodeGen...');
|
|
84
|
+
this.renameFolderToMjBase(userConfig.dbDatabase);
|
|
85
|
+
dotenv.config();
|
|
86
|
+
this.cmd.config.runCommand('codegen');
|
|
87
|
+
}
|
|
88
|
+
async processMjExplorer(userConfig) {
|
|
89
|
+
this.cmd.log('\nProcessing MJExplorer...');
|
|
90
|
+
this.cmd.log('\n Updating environment files...');
|
|
91
|
+
const config = {
|
|
92
|
+
CLIENT_ID: userConfig.msalWebClientId,
|
|
93
|
+
TENANT_ID: userConfig.msalTenantId,
|
|
94
|
+
CLIENT_AUTHORITY: userConfig.msalTenantId
|
|
95
|
+
? `https://login.microsoftonline.com/${userConfig.msalTenantId}`
|
|
96
|
+
: '',
|
|
97
|
+
AUTH_TYPE: userConfig.authType === 'AUTH0' ? 'auth0' : userConfig.authType.toLowerCase(),
|
|
98
|
+
AUTH0_DOMAIN: userConfig.auth0Domain,
|
|
99
|
+
AUTH0_CLIENTID: userConfig.auth0ClientId,
|
|
100
|
+
};
|
|
101
|
+
await this.updateEnvironmentFiles(path.join(MJEXPLORER_DIR, 'src', 'environments'), config);
|
|
102
|
+
this.cmd.log(' Running npm link for GeneratedEntities...');
|
|
103
|
+
execSync('npm link ../GeneratedEntities', { stdio: 'inherit', cwd: MJEXPLORER_DIR });
|
|
104
|
+
}
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Preflight checks
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
checkNodeVersion() {
|
|
109
|
+
const validNodeVersion = Number(process.version.replace(/^v(\d+).*/, '$1')) >= 20;
|
|
110
|
+
if (!validNodeVersion) {
|
|
111
|
+
this.cmd.error('MemberJunction requires Node.js version 20 or higher.', { exit: 1 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
checkAvailableDiskSpace(numGB = 2) {
|
|
115
|
+
try {
|
|
116
|
+
this.cmd.log(`Checking for at least ${numGB}GB of free disk space...`);
|
|
117
|
+
const GBToBytes = 1024 * 1024 * 1024;
|
|
118
|
+
const requiredSpace = numGB * GBToBytes;
|
|
119
|
+
let freeSpace;
|
|
120
|
+
if (os.platform() === 'win32') {
|
|
121
|
+
const command = `wmic LogicalDisk where DeviceID="C:" get FreeSpace`;
|
|
122
|
+
const output = execSync(command).toString();
|
|
123
|
+
const lines = output.trim().split('\n');
|
|
124
|
+
freeSpace = parseInt(lines[1].trim());
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const command = `df -k / | tail -1 | awk '{ print $4; }'`;
|
|
128
|
+
freeSpace = parseInt(execSync(command).toString().trim()) * 1024;
|
|
129
|
+
}
|
|
130
|
+
if (freeSpace >= requiredSpace) {
|
|
131
|
+
this.cmd.log(` Sufficient disk space available: ${Math.round(freeSpace / GBToBytes)} GB`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
this.cmd.error(`Insufficient disk space. Required: ${requiredSpace} bytes, Available: ${Math.round(freeSpace / GBToBytes)} GB`, { exit: 1 });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
this.cmd.error('Error checking disk space', { exit: 1 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
verifyDirs(...dirs) {
|
|
142
|
+
dirs.forEach((dir) => {
|
|
143
|
+
if (!fs.existsSync(dir)) {
|
|
144
|
+
this.cmd.error(`Unable to locate required package at '${path.join(fs.realpathSync('.'), dir)}'`, {
|
|
145
|
+
exit: 1,
|
|
146
|
+
suggestions: ['Run the install from the same directory as the extracted MemberJunction distribution'],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Configuration
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
async getUserConfiguration() {
|
|
155
|
+
let userConfig;
|
|
156
|
+
try {
|
|
157
|
+
const configObject = await fs.readJSON('install.config.json');
|
|
158
|
+
userConfig = configSchema.parse(configObject);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
if (e instanceof ZodError) {
|
|
162
|
+
this.cmd.log(`Invalid config file found at '${path.join(fs.realpathSync('.'), 'install.config.json')}'${this.verbose ? '' : ', retry with --verbose for details'}`);
|
|
163
|
+
if (this.verbose) {
|
|
164
|
+
console.table(e.issues);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
this.cmd.log(`No config file found at '${path.join(fs.realpathSync('.'), 'install.config.json')}'`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!userConfig) {
|
|
172
|
+
userConfig = await this.promptForConfiguration();
|
|
173
|
+
}
|
|
174
|
+
return userConfig;
|
|
175
|
+
}
|
|
176
|
+
async promptForConfiguration() {
|
|
177
|
+
this.cmd.log('\n>>> Please answer the following questions to setup the .env files for CodeGen. After this process you can manually edit the .env file as desired.');
|
|
178
|
+
const dbUrl = await input({
|
|
179
|
+
message: 'Enter the database server hostname:',
|
|
180
|
+
validate: (v) => configSchema.shape.dbDatabase.safeParse(v).success,
|
|
181
|
+
});
|
|
182
|
+
const dbInstance = await input({
|
|
183
|
+
message: 'If you are using a named instance on that server, if so, enter the name here, if not leave blank:',
|
|
184
|
+
});
|
|
185
|
+
const dbTrustServerCertificate = (await confirm({
|
|
186
|
+
message: 'Does the database server use a self-signed certificate? If you are using a local instance, enter Y:',
|
|
187
|
+
}))
|
|
188
|
+
? 'Y'
|
|
189
|
+
: 'N';
|
|
190
|
+
const dbDatabase = await input({
|
|
191
|
+
message: 'Enter the database name on that server:',
|
|
192
|
+
validate: (v) => configSchema.shape.dbDatabase.safeParse(v).success,
|
|
193
|
+
});
|
|
194
|
+
const dbPort = await input({
|
|
195
|
+
message: 'Enter the port the database server listens on',
|
|
196
|
+
validate: (v) => configSchema.shape.dbPort.safeParse(v).success,
|
|
197
|
+
default: '1433',
|
|
198
|
+
});
|
|
199
|
+
const codeGenLogin = await input({ message: 'Enter the database login for CodeGen:' });
|
|
200
|
+
const codeGenPwD = await input({ message: 'Enter the database password for CodeGen:' });
|
|
201
|
+
this.cmd.log('\n>>> Please answer the following questions to setup the .env files for MJAPI. After this process you can manually edit the .env file in CodeGen as desired.');
|
|
202
|
+
const mjAPILogin = await input({ message: 'Enter the database login for MJAPI:' });
|
|
203
|
+
const mjAPIPwD = await input({ message: 'Enter the database password for MJAPI:' });
|
|
204
|
+
const graphQLPort = await input({
|
|
205
|
+
message: 'Enter the port to use for the GraphQL API',
|
|
206
|
+
validate: (v) => configSchema.shape.graphQLPort.safeParse(v).success,
|
|
207
|
+
default: '4000',
|
|
208
|
+
});
|
|
209
|
+
const authConfig = await this.promptForAuthConfiguration();
|
|
210
|
+
const userSetup = await this.promptForUserSetup();
|
|
211
|
+
const aiKeys = await this.promptForAiKeys();
|
|
212
|
+
return configSchema.parse({
|
|
213
|
+
dbUrl,
|
|
214
|
+
dbInstance,
|
|
215
|
+
dbTrustServerCertificate,
|
|
216
|
+
dbDatabase,
|
|
217
|
+
dbPort,
|
|
218
|
+
codeGenLogin,
|
|
219
|
+
codeGenPwD,
|
|
220
|
+
mjAPILogin,
|
|
221
|
+
mjAPIPwD,
|
|
222
|
+
graphQLPort,
|
|
223
|
+
...authConfig,
|
|
224
|
+
...userSetup,
|
|
225
|
+
...aiKeys,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
async promptForAuthConfiguration() {
|
|
229
|
+
const authType = await select({
|
|
230
|
+
message: 'Will you be using Microsoft Entra (formerly Azure AD), Auth0, or both for authentication services for MJAPI:',
|
|
231
|
+
choices: [
|
|
232
|
+
{ name: 'Microsoft Entra (MSAL)', value: 'MSAL' },
|
|
233
|
+
{ name: 'Auth0', value: 'AUTH0' },
|
|
234
|
+
{ name: 'Both', value: 'BOTH' },
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
const msalTenantId = ['BOTH', 'MSAL'].includes(authType) ? await input({ message: 'Enter the web client ID for Entra:' }) : '';
|
|
238
|
+
const msalWebClientId = ['BOTH', 'MSAL'].includes(authType) ? await input({ message: 'Enter the tenant ID for Entra:' }) : '';
|
|
239
|
+
const auth0ClientId = ['BOTH', 'AUTH0'].includes(authType) ? await input({ message: 'Enter the client ID for Auth0:' }) : '';
|
|
240
|
+
const auth0ClientSecret = ['BOTH', 'AUTH0'].includes(authType) ? await input({ message: 'Enter the client secret for Auth0:' }) : '';
|
|
241
|
+
const auth0Domain = ['BOTH', 'AUTH0'].includes(authType) ? await input({ message: 'Enter the domain for Auth0:' }) : '';
|
|
242
|
+
return { authType, msalTenantId, msalWebClientId, auth0ClientId, auth0ClientSecret, auth0Domain };
|
|
243
|
+
}
|
|
244
|
+
async promptForUserSetup() {
|
|
245
|
+
const createNewUser = await confirm({ message: 'Do you want to create a new user in the database? (Y/N):' });
|
|
246
|
+
const userEmail = createNewUser
|
|
247
|
+
? await input({
|
|
248
|
+
message: 'Enter the new user email',
|
|
249
|
+
validate: (v) => configSchema.shape.userEmail.safeParse(v).success,
|
|
250
|
+
})
|
|
251
|
+
: '';
|
|
252
|
+
const userFirstName = createNewUser ? await input({ message: 'Enter the new user first name:' }) : '';
|
|
253
|
+
const userLastName = createNewUser ? await input({ message: 'Enter the new user last name::' }) : '';
|
|
254
|
+
const userName = createNewUser
|
|
255
|
+
? await input({ message: 'Enter the new user name (leave blank to use email):', default: userEmail })
|
|
256
|
+
: '';
|
|
257
|
+
return { createNewUser: createNewUser ? 'Y' : 'N', userEmail, userFirstName, userLastName, userName };
|
|
258
|
+
}
|
|
259
|
+
async promptForAiKeys() {
|
|
260
|
+
const openAIAPIKey = await input({ message: 'Enter the OpenAI API Key (leave blank if not using):' });
|
|
261
|
+
const anthropicAPIKey = await input({ message: 'Enter the Anthropic API Key (leave blank if not using):' });
|
|
262
|
+
const mistralAPIKey = await input({ message: 'Enter the Mistral API Key (leave blank if not using):' });
|
|
263
|
+
return { openAIAPIKey, anthropicAPIKey, mistralAPIKey };
|
|
264
|
+
}
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// File generation
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
writeEnvFile(userConfig) {
|
|
269
|
+
this.cmd.log('\nProcessing Config...');
|
|
270
|
+
this.cmd.log(' Updating ');
|
|
271
|
+
this.cmd.log(' Setting up .env and mj.config.cjs...');
|
|
272
|
+
const dotenvContent = `#Database Setup
|
|
273
|
+
DB_HOST='${userConfig.dbUrl}'
|
|
274
|
+
DB_PORT=${userConfig.dbPort}
|
|
275
|
+
CODEGEN_DB_USERNAME='${userConfig.codeGenLogin}'
|
|
276
|
+
CODEGEN_DB_PASSWORD='${userConfig.codeGenPwD}'
|
|
277
|
+
DB_USERNAME='${userConfig.mjAPILogin}'
|
|
278
|
+
DB_PASSWORD='${userConfig.mjAPIPwD}'
|
|
279
|
+
DB_DATABASE='${userConfig.dbDatabase}'
|
|
280
|
+
${userConfig.dbInstance ? "DB_INSTANCE_NAME='" + userConfig.dbInstance + "'" : ''}
|
|
281
|
+
${userConfig.dbTrustServerCertificate === 'Y' ? 'DB_TRUST_SERVER_CERTIFICATE=1' : ''}
|
|
282
|
+
|
|
283
|
+
#OUTPUT CODE is used for output directories like SQL Scripts
|
|
284
|
+
OUTPUT_CODE='${userConfig.dbDatabase}'
|
|
285
|
+
|
|
286
|
+
# Name of the schema that MJ has been setup in. This defaults to __mj
|
|
287
|
+
MJ_CORE_SCHEMA='__mj'
|
|
288
|
+
|
|
289
|
+
# If using Advanced Generation or the MJAI library, populate this with the API key for the AI vendor you are using
|
|
290
|
+
# Also, you need to configure the settings under advancedGeneration in the mj.config.cjs file, including choosing the vendor.
|
|
291
|
+
AI_VENDOR_API_KEY__OpenAILLM='${userConfig.openAIAPIKey}'
|
|
292
|
+
AI_VENDOR_API_KEY__MistralLLM='${userConfig.mistralAPIKey}'
|
|
293
|
+
AI_VENDOR_API_KEY__AnthropicLLM='${userConfig.anthropicAPIKey}'
|
|
294
|
+
|
|
295
|
+
PORT=${userConfig.graphQLPort}
|
|
296
|
+
|
|
297
|
+
UPDATE_USER_CACHE_WHEN_NOT_FOUND=1
|
|
298
|
+
UPDATE_USER_CACHE_WHEN_NOT_FOUND_DELAY=5000
|
|
299
|
+
|
|
300
|
+
# AUTHENTICATION SECTION - you can use MSAL or Auth0 or both for authentication services for MJAPI
|
|
301
|
+
# MSAL Section
|
|
302
|
+
WEB_CLIENT_ID=${userConfig.msalWebClientId}
|
|
303
|
+
TENANT_ID=${userConfig.msalTenantId}
|
|
304
|
+
|
|
305
|
+
# Auth0 Section
|
|
306
|
+
AUTH0_CLIENT_ID=${userConfig.auth0ClientId}
|
|
307
|
+
AUTH0_CLIENT_SECRET=${userConfig.auth0ClientSecret}
|
|
308
|
+
AUTH0_DOMAIN=${userConfig.auth0Domain}
|
|
309
|
+
|
|
310
|
+
# Skip API URL, KEY and Org ID
|
|
311
|
+
# YOU MUST ENTER IN THE CORRECT URL and ORG ID for your Skip API USE BELOW
|
|
312
|
+
ASK_SKIP_API_URL = 'http://localhost:8000'
|
|
313
|
+
ASK_SKIP_ORGANIZATION_ID = 1
|
|
314
|
+
`;
|
|
315
|
+
fs.writeFileSync('.env', dotenvContent);
|
|
316
|
+
}
|
|
317
|
+
renameFolderToMjBase(dbDatabase) {
|
|
318
|
+
const oldFolderPath = path.join(SQL_SCRIPTS_DIR, GENERATED_DIR, MJ_BASE_DIR);
|
|
319
|
+
const newFolderPath = path.join(SQL_SCRIPTS_DIR, GENERATED_DIR, dbDatabase);
|
|
320
|
+
if (!fs.existsSync(oldFolderPath)) {
|
|
321
|
+
this.cmd.warn(`SQL scripts not found at '${oldFolderPath}', skipping rename`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
fs.moveSync(oldFolderPath, newFolderPath);
|
|
326
|
+
this.cmd.log(`Renamed ${oldFolderPath} to ${newFolderPath} successfully.`);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
this.cmd.logToStderr(`An error occurred while renaming the '${oldFolderPath}' folder: ${err instanceof Error ? err.message : String(err)}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async updateEnvironmentFiles(dirPath, config) {
|
|
333
|
+
try {
|
|
334
|
+
const envFilePattern = /environment.*\.ts$/;
|
|
335
|
+
const files = await fs.readdir(dirPath);
|
|
336
|
+
const envFiles = files.filter((file) => envFilePattern.test(file));
|
|
337
|
+
for (const file of envFiles) {
|
|
338
|
+
if (this.verbose) {
|
|
339
|
+
this.cmd.log(`Updating ${file}`);
|
|
340
|
+
}
|
|
341
|
+
const filePath = path.join(dirPath, file);
|
|
342
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
343
|
+
let updatedData = data;
|
|
344
|
+
Object.entries(config).forEach(([key, value = '']) => {
|
|
345
|
+
const regex = new RegExp(`(["\']?${key}["\']?:\\s*["\'])([^"\']*)(["\'])`, 'g');
|
|
346
|
+
const escapedValue = value.replaceAll('$', () => '$$');
|
|
347
|
+
updatedData = updatedData.replace(regex, `$1${escapedValue}$3`);
|
|
348
|
+
});
|
|
349
|
+
await fs.writeFile(filePath, updatedData, 'utf8');
|
|
350
|
+
this.cmd.log(`Updated ${file}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
console.error('Error:', err);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async UpdateConfigNewUserSetup(userName, firstName, lastName, email) {
|
|
358
|
+
try {
|
|
359
|
+
const configFileContent = await fs.readFile('mj.config.cjs', 'utf8');
|
|
360
|
+
const ast = recast.parse(configFileContent);
|
|
361
|
+
const n = recast.types.namedTypes;
|
|
362
|
+
const b = recast.types.builders;
|
|
363
|
+
recast.types.visit(ast, {
|
|
364
|
+
visitObjectExpression(visitPath) {
|
|
365
|
+
const properties = visitPath.node.properties;
|
|
366
|
+
const newUserSetupProperty = properties.find((prop) => n.Property.check(prop) && n.Identifier.check(prop.key) && prop.key.name === 'newUserSetup');
|
|
367
|
+
const newUserSetupValue = b.objectExpression([
|
|
368
|
+
b.property('init', b.identifier('userName'), b.literal(userName || '')),
|
|
369
|
+
b.property('init', b.identifier('firstName'), b.literal(firstName || '')),
|
|
370
|
+
b.property('init', b.identifier('lastName'), b.literal(lastName || '')),
|
|
371
|
+
b.property('init', b.identifier('email'), b.literal(email || '')),
|
|
372
|
+
]);
|
|
373
|
+
if (newUserSetupProperty && newUserSetupProperty.type === 'Property') {
|
|
374
|
+
newUserSetupProperty.value = newUserSetupValue;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
properties.push(b.property('init', b.identifier('newUserSetup'), newUserSetupValue));
|
|
378
|
+
}
|
|
379
|
+
return false;
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
const updatedConfigFileContent = recast.prettyPrint(ast).code;
|
|
383
|
+
await fs.writeFile('mj.config.cjs', updatedConfigFileContent);
|
|
384
|
+
this.cmd.log(` Updated mj.config.cjs`);
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
this.cmd.logToStderr(`Error updating mj.config.cjs: ${err instanceof Error ? err.message : String(err)}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
//# sourceMappingURL=legacy-install.js.map
|