@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
|
@@ -1,418 +1,217 @@
|
|
|
1
|
-
import { confirm, input
|
|
2
|
-
import dotenv from 'dotenv';
|
|
3
|
-
import recast from 'recast';
|
|
1
|
+
import { select, confirm, input } from '@inquirer/prompts';
|
|
4
2
|
import { Command, Flags } from '@oclif/core';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import { ZodError, z } from 'zod';
|
|
10
|
-
// Directories are relative to execution cwd
|
|
11
|
-
const GENERATED_ENTITIES_DIR = 'GeneratedEntities';
|
|
12
|
-
const SQL_SCRIPTS_DIR = 'SQL Scripts';
|
|
13
|
-
const GENERATED_DIR = 'generated';
|
|
14
|
-
const MJ_BASE_DIR = 'MJ_BASE';
|
|
15
|
-
const MJAPI_DIR = 'MJAPI';
|
|
16
|
-
const MJEXPLORER_DIR = 'MJExplorer';
|
|
17
|
-
const configSchema = z.object({
|
|
18
|
-
dbUrl: z.string().min(1),
|
|
19
|
-
dbInstance: z.string(),
|
|
20
|
-
dbTrustServerCertificate: z.coerce
|
|
21
|
-
.boolean()
|
|
22
|
-
.default(false)
|
|
23
|
-
.transform((v) => (v ? 'Y' : 'N')),
|
|
24
|
-
dbDatabase: z.string().min(1),
|
|
25
|
-
dbPort: z.number({ coerce: true }).int().positive(),
|
|
26
|
-
codeGenLogin: z.string(),
|
|
27
|
-
codeGenPwD: z.string(),
|
|
28
|
-
mjAPILogin: z.string(),
|
|
29
|
-
mjAPIPwD: z.string(),
|
|
30
|
-
graphQLPort: z.number({ coerce: true }).int().positive().optional(),
|
|
31
|
-
authType: z.enum(['MSAL', 'AUTH0', 'BOTH']),
|
|
32
|
-
msalWebClientId: z.string().optional(),
|
|
33
|
-
msalTenantId: z.string().optional(),
|
|
34
|
-
auth0ClientId: z.string().optional(),
|
|
35
|
-
auth0ClientSecret: z.string().optional(),
|
|
36
|
-
auth0Domain: z.string().optional(),
|
|
37
|
-
createNewUser: z.coerce.boolean().optional(),
|
|
38
|
-
userEmail: z.string().email().or(z.literal('')).optional().default(''),
|
|
39
|
-
userFirstName: z.string().optional(),
|
|
40
|
-
userLastName: z.string().optional(),
|
|
41
|
-
userName: z.string().optional(),
|
|
42
|
-
openAIAPIKey: z.string().optional(),
|
|
43
|
-
anthropicAPIKey: z.string().optional(),
|
|
44
|
-
mistralAPIKey: z.string().optional(),
|
|
45
|
-
});
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { InstallerEngine, } from '@memberjunction/installer';
|
|
5
|
+
import { LegacyInstaller } from '../../lib/legacy-install.js';
|
|
46
6
|
export default class Install extends Command {
|
|
47
|
-
static { this.description = 'Install MemberJunction'; }
|
|
7
|
+
static { this.description = 'Install MemberJunction from a GitHub release'; }
|
|
48
8
|
static { this.examples = [
|
|
49
|
-
|
|
50
|
-
|
|
9
|
+
'<%= config.bin %> <%= command.id %>',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> -t v4.3.0',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --dry-run',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> --yes',
|
|
51
13
|
]; }
|
|
52
14
|
static { this.flags = {
|
|
53
|
-
|
|
15
|
+
tag: Flags.string({
|
|
16
|
+
char: 't',
|
|
17
|
+
description: 'Release tag to install (e.g. v4.3.0). If omitted, shows a version picker.',
|
|
18
|
+
}),
|
|
19
|
+
dir: Flags.string({
|
|
20
|
+
description: 'Target directory for the installation',
|
|
21
|
+
default: '.',
|
|
22
|
+
}),
|
|
23
|
+
config: Flags.string({
|
|
24
|
+
char: 'c',
|
|
25
|
+
description: 'Path to JSON config file with install settings (database, auth, ports, etc.)',
|
|
26
|
+
}),
|
|
27
|
+
legacy: Flags.boolean({
|
|
28
|
+
description: 'Use the legacy interactive installer (ZIP-only distribution)',
|
|
29
|
+
hidden: true,
|
|
30
|
+
}),
|
|
31
|
+
'dry-run': Flags.boolean({
|
|
32
|
+
description: 'Show the install plan without executing it',
|
|
33
|
+
}),
|
|
34
|
+
yes: Flags.boolean({
|
|
35
|
+
char: 'y',
|
|
36
|
+
description: 'Non-interactive mode: auto-select latest version and accept defaults',
|
|
37
|
+
}),
|
|
38
|
+
verbose: Flags.boolean({
|
|
39
|
+
char: 'v',
|
|
40
|
+
description: 'Show detailed output',
|
|
41
|
+
}),
|
|
42
|
+
'skip-db': Flags.boolean({
|
|
43
|
+
description: 'Skip database provisioning phases',
|
|
44
|
+
}),
|
|
45
|
+
'skip-start': Flags.boolean({
|
|
46
|
+
description: 'Skip service startup and smoke tests',
|
|
47
|
+
}),
|
|
48
|
+
'no-resume': Flags.boolean({
|
|
49
|
+
description: 'Ignore any existing checkpoint and start fresh',
|
|
50
|
+
}),
|
|
51
|
+
fast: Flags.boolean({
|
|
52
|
+
description: 'Fast mode: skip smoke test and optimize post-codegen steps. Re-run without --fast if you encounter issues.',
|
|
53
|
+
}),
|
|
54
54
|
}; }
|
|
55
55
|
async run() {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.verifyDirs(GENERATED_ENTITIES_DIR, SQL_SCRIPTS_DIR, MJAPI_DIR, MJEXPLORER_DIR);
|
|
61
|
-
this.userConfig = await this.getUserConfiguration();
|
|
62
|
-
this.log('Setting up MemberJunction Distribution...');
|
|
63
|
-
if (this.flags.verbose) {
|
|
64
|
-
this.logJson({ userConfig: this.userConfig, flags: this.flags });
|
|
56
|
+
const { flags } = await this.parse(Install);
|
|
57
|
+
if (flags.legacy) {
|
|
58
|
+
const legacy = new LegacyInstaller(this, flags.verbose ?? false);
|
|
59
|
+
return legacy.Run();
|
|
65
60
|
}
|
|
66
|
-
|
|
67
|
-
// Process GeneratedEntities
|
|
68
|
-
//*******************************************************************
|
|
69
|
-
this.log('\nBootstrapping GeneratedEntities...');
|
|
70
|
-
this.log('Running npm install...');
|
|
71
|
-
execSync('npm install', { stdio: 'inherit', cwd: GENERATED_ENTITIES_DIR });
|
|
72
|
-
//*******************************************************************
|
|
73
|
-
// Process Config
|
|
74
|
-
//*******************************************************************
|
|
75
|
-
this.log('\nProcessing Config...');
|
|
76
|
-
this.log(' Updating ');
|
|
77
|
-
this.log(' Setting up .env and mj.config.cjs...');
|
|
78
|
-
const dotenvContent = `#Database Setup
|
|
79
|
-
DB_HOST='${this.userConfig.dbUrl}'
|
|
80
|
-
DB_PORT=${this.userConfig.dbPort}
|
|
81
|
-
CODEGEN_DB_USERNAME='${this.userConfig.codeGenLogin}'
|
|
82
|
-
CODEGEN_DB_PASSWORD='${this.userConfig.codeGenPwD}'
|
|
83
|
-
DB_USERNAME='${this.userConfig.mjAPILogin}'
|
|
84
|
-
DB_PASSWORD='${this.userConfig.mjAPIPwD}'
|
|
85
|
-
DB_DATABASE='${this.userConfig.dbDatabase}'
|
|
86
|
-
${this.userConfig.dbInstance ? "DB_INSTANCE_NAME='" + this.userConfig.dbInstance + "'" : ''}
|
|
87
|
-
${this.userConfig.dbTrustServerCertificate === 'Y' ? 'DB_TRUST_SERVER_CERTIFICATE=1' : ''}
|
|
88
|
-
|
|
89
|
-
#OUTPUT CODE is used for output directories like SQL Scripts
|
|
90
|
-
OUTPUT_CODE='${this.userConfig.dbDatabase}'
|
|
91
|
-
|
|
92
|
-
# Name of the schema that MJ has been setup in. This defaults to __mj
|
|
93
|
-
MJ_CORE_SCHEMA='__mj'
|
|
94
|
-
|
|
95
|
-
# If using Advanced Generation or the MJAI library, populate this with the API key for the AI vendor you are using
|
|
96
|
-
# Also, you need to configure the settings under advancedGeneration in the mj.config.cjs file, including choosing the vendor.
|
|
97
|
-
AI_VENDOR_API_KEY__OpenAILLM='${this.userConfig.openAIAPIKey}'
|
|
98
|
-
AI_VENDOR_API_KEY__MistralLLM='${this.userConfig.mistralAPIKey}'
|
|
99
|
-
AI_VENDOR_API_KEY__AnthropicLLM='${this.userConfig.anthropicAPIKey}'
|
|
100
|
-
|
|
101
|
-
PORT=${this.userConfig.graphQLPort}
|
|
102
|
-
|
|
103
|
-
UPDATE_USER_CACHE_WHEN_NOT_FOUND=1
|
|
104
|
-
UPDATE_USER_CACHE_WHEN_NOT_FOUND_DELAY=5000
|
|
105
|
-
|
|
106
|
-
# AUTHENTICATION SECTION - you can use MSAL or Auth0 or both for authentication services for MJAPI
|
|
107
|
-
# MSAL Section
|
|
108
|
-
WEB_CLIENT_ID=${this.userConfig.msalWebClientId}
|
|
109
|
-
TENANT_ID=${this.userConfig.msalTenantId}
|
|
110
|
-
|
|
111
|
-
# Auth0 Section
|
|
112
|
-
AUTH0_CLIENT_ID=${this.userConfig.auth0ClientId}
|
|
113
|
-
AUTH0_CLIENT_SECRET=${this.userConfig.auth0ClientSecret}
|
|
114
|
-
AUTH0_DOMAIN=${this.userConfig.auth0Domain}
|
|
115
|
-
|
|
116
|
-
# Skip API URL, KEY and Org ID
|
|
117
|
-
# YOU MUST ENTER IN THE CORRECT URL and ORG ID for your Skip API USE BELOW
|
|
118
|
-
ASK_SKIP_API_URL = 'http://localhost:8000'
|
|
119
|
-
ASK_SKIP_ORGANIZATION_ID = 1
|
|
120
|
-
`;
|
|
121
|
-
fs.writeFileSync('.env', dotenvContent);
|
|
122
|
-
//*******************************************************************
|
|
123
|
-
// Process MJAPI
|
|
124
|
-
//*******************************************************************
|
|
125
|
-
this.log('\n\nBootstrapping MJAPI...');
|
|
126
|
-
this.log(' Running npm link for generated code...');
|
|
127
|
-
execSync('npm link ../GeneratedEntities ../GeneratedActions', { stdio: 'inherit', cwd: MJAPI_DIR });
|
|
128
|
-
this.log('Running CodeGen...');
|
|
129
|
-
this.renameFolderToMJ_BASE(this.userConfig.dbDatabase);
|
|
130
|
-
// next, run CodeGen
|
|
131
|
-
// We do not manually run the compilation for GeneratedEntities because CodeGen handles that, but notice above that we did npm install for GeneratedEntities otherwise when CodeGen attempts to compile it, it will fail.
|
|
132
|
-
dotenv.config({ quiet: true });
|
|
133
|
-
this.config.runCommand('codegen');
|
|
134
|
-
// Process MJExplorer
|
|
135
|
-
this.log('\nProcessing MJExplorer...');
|
|
136
|
-
this.log('\n Updating environment files...');
|
|
137
|
-
const config = {
|
|
138
|
-
CLIENT_ID: this.userConfig.msalWebClientId,
|
|
139
|
-
TENANT_ID: this.userConfig.msalTenantId,
|
|
140
|
-
CLIENT_AUTHORITY: this.userConfig.msalTenantId ? `https://login.microsoftonline.com/${this.userConfig.msalTenantId}` : '',
|
|
141
|
-
AUTH_TYPE: this.userConfig.authType === 'AUTH0' ? 'auth0' : this.userConfig.authType.toLowerCase(),
|
|
142
|
-
AUTH0_DOMAIN: this.userConfig.auth0Domain,
|
|
143
|
-
AUTH0_CLIENTID: this.userConfig.auth0ClientId,
|
|
144
|
-
};
|
|
145
|
-
await this.updateEnvironmentFiles(path.join(MJEXPLORER_DIR, 'src', 'environments'), config);
|
|
146
|
-
// keep on going with MJ Explorer - do the rest of the stuff
|
|
147
|
-
this.log(' Running npm link for GeneratedEntities...');
|
|
148
|
-
execSync('npm link ../GeneratedEntities', { stdio: 'inherit', cwd: MJEXPLORER_DIR });
|
|
149
|
-
this.log('Installation complete!');
|
|
61
|
+
return this.runEngineInstall(flags);
|
|
150
62
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
else {
|
|
165
|
-
this.log(`No config file found at '${path.join(fs.realpathSync('.'), 'install.config.json')}'`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (!userConfig) {
|
|
169
|
-
this.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.');
|
|
170
|
-
const dbUrl = await input({
|
|
171
|
-
message: 'Enter the database server hostname:',
|
|
172
|
-
validate: (v) => configSchema.shape.dbDatabase.safeParse(v).success,
|
|
173
|
-
});
|
|
174
|
-
const dbInstance = await input({
|
|
175
|
-
message: 'If you are using a named instance on that server, if so, enter the name here, if not leave blank:',
|
|
176
|
-
});
|
|
177
|
-
const dbTrustServerCertificate = (await confirm({
|
|
178
|
-
message: 'Does the database server use a self-signed certificate? If you are using a local instance, enter Y:',
|
|
179
|
-
}))
|
|
180
|
-
? 'Y'
|
|
181
|
-
: 'N';
|
|
182
|
-
const dbDatabase = await input({
|
|
183
|
-
message: 'Enter the database name on that server:',
|
|
184
|
-
validate: (v) => configSchema.shape.dbDatabase.safeParse(v).success,
|
|
185
|
-
});
|
|
186
|
-
const dbPort = await input({
|
|
187
|
-
message: 'Enter the port the database server listens on',
|
|
188
|
-
validate: (v) => configSchema.shape.dbPort.safeParse(v).success,
|
|
189
|
-
default: '1433',
|
|
190
|
-
});
|
|
191
|
-
const codeGenLogin = await input({ message: 'Enter the database login for CodeGen:' });
|
|
192
|
-
const codeGenPwD = await input({ message: 'Enter the database password for CodeGen:' });
|
|
193
|
-
this.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.');
|
|
194
|
-
const mjAPILogin = await input({ message: 'Enter the database login for MJAPI:' });
|
|
195
|
-
const mjAPIPwD = await input({ message: 'Enter the database password for MJAPI:' });
|
|
196
|
-
const graphQLPort = await input({
|
|
197
|
-
message: 'Enter the port to use for the GraphQL API',
|
|
198
|
-
validate: (v) => configSchema.shape.graphQLPort.safeParse(v).success,
|
|
199
|
-
default: '4000',
|
|
200
|
-
});
|
|
201
|
-
const authType = await select({
|
|
202
|
-
message: 'Will you be using Microsoft Entra (formerly Azure AD), Auth0, or both for authentication services for MJAPI:',
|
|
203
|
-
choices: [
|
|
204
|
-
{ name: 'Microsoft Entra (MSAL)', value: 'MSAL' },
|
|
205
|
-
{ name: 'Auth0', value: 'AUTH0' },
|
|
206
|
-
{ name: 'Both', value: 'BOTH' },
|
|
207
|
-
],
|
|
208
|
-
});
|
|
209
|
-
const msalTenantId = ['BOTH', 'MSAL'].includes(authType) ? await input({ message: 'Enter the web client ID for Entra:' }) : '';
|
|
210
|
-
const msalWebClientId = ['BOTH', 'MSAL'].includes(authType) ? await input({ message: 'Enter the tenant ID for Entra:' }) : '';
|
|
211
|
-
const auth0ClientId = ['BOTH', 'AUTH0'].includes(authType) ? await input({ message: 'Enter the client ID for Auth0:' }) : '';
|
|
212
|
-
const auth0ClientSecret = ['BOTH', 'AUTH0'].includes(authType) ? await input({ message: 'Enter the client secret for Auth0:' }) : '';
|
|
213
|
-
const auth0Domain = ['BOTH', 'AUTH0'].includes(authType) ? await input({ message: 'Enter the domain for Auth0:' }) : '';
|
|
214
|
-
const createNewUser = await confirm({ message: 'Do you want to create a new user in the database? (Y/N):' });
|
|
215
|
-
const userEmail = createNewUser
|
|
216
|
-
? await input({
|
|
217
|
-
message: 'Enter the new user email',
|
|
218
|
-
validate: (v) => configSchema.shape.userEmail.safeParse(v).success,
|
|
219
|
-
})
|
|
220
|
-
: '';
|
|
221
|
-
const userFirstName = createNewUser ? await input({ message: 'Enter the new user first name:' }) : '';
|
|
222
|
-
const userLastName = createNewUser ? await input({ message: 'Enter the new user last name::' }) : '';
|
|
223
|
-
const userName = createNewUser
|
|
224
|
-
? await input({ message: 'Enter the new user name (leave blank to use email):', default: userEmail })
|
|
225
|
-
: '';
|
|
226
|
-
const openAIAPIKey = await input({ message: 'Enter the OpenAI API Key (leave blank if not using):' });
|
|
227
|
-
const anthropicAPIKey = await input({ message: 'Enter the Anthropic API Key (leave blank if not using):' });
|
|
228
|
-
const mistralAPIKey = await input({ message: 'Enter the Mistral API Key (leave blank if not using):' });
|
|
229
|
-
userConfig = configSchema.parse({
|
|
230
|
-
dbUrl,
|
|
231
|
-
dbInstance,
|
|
232
|
-
dbTrustServerCertificate,
|
|
233
|
-
dbDatabase,
|
|
234
|
-
dbPort,
|
|
235
|
-
codeGenLogin,
|
|
236
|
-
codeGenPwD,
|
|
237
|
-
mjAPILogin,
|
|
238
|
-
mjAPIPwD,
|
|
239
|
-
graphQLPort,
|
|
240
|
-
authType,
|
|
241
|
-
msalWebClientId,
|
|
242
|
-
msalTenantId,
|
|
243
|
-
auth0ClientId,
|
|
244
|
-
auth0ClientSecret,
|
|
245
|
-
auth0Domain,
|
|
246
|
-
createNewUser: createNewUser ? 'Y' : 'N',
|
|
247
|
-
userEmail,
|
|
248
|
-
userFirstName,
|
|
249
|
-
userLastName,
|
|
250
|
-
userName,
|
|
251
|
-
openAIAPIKey,
|
|
252
|
-
anthropicAPIKey,
|
|
253
|
-
mistralAPIKey,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return userConfig;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Verifies that the specified directories exist.
|
|
260
|
-
* @param {...string} dirs - The directories to check.
|
|
261
|
-
*/
|
|
262
|
-
verifyDirs(...dirs) {
|
|
263
|
-
dirs.forEach((dir) => {
|
|
264
|
-
if (!fs.existsSync(dir)) {
|
|
265
|
-
this.error(`Unable to locate required package at '${path.join(fs.realpathSync('.'), dir)}'`, {
|
|
266
|
-
exit: 1,
|
|
267
|
-
suggestions: ['Run the install from the same directory as the extracted MemberJunction distribution'],
|
|
268
|
-
});
|
|
269
|
-
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Engine-based install
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
async runEngineInstall(flags) {
|
|
67
|
+
const engine = new InstallerEngine();
|
|
68
|
+
const fast = flags.fast ?? false;
|
|
69
|
+
this.wireEventHandlers(engine, flags.verbose ?? false);
|
|
70
|
+
const plan = await engine.CreatePlan({
|
|
71
|
+
Tag: flags.tag,
|
|
72
|
+
Dir: flags.dir,
|
|
73
|
+
SkipDB: flags['skip-db'],
|
|
74
|
+
SkipStart: flags['skip-start'],
|
|
75
|
+
Fast: fast,
|
|
270
76
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!validNodeVersion) {
|
|
275
|
-
this.error('MemberJunction requires Node.js version 20 or higher.', { exit: 1 });
|
|
77
|
+
if (flags['dry-run']) {
|
|
78
|
+
this.renderDryRun(plan);
|
|
79
|
+
return;
|
|
276
80
|
}
|
|
81
|
+
this.renderHeader();
|
|
82
|
+
const result = await engine.Run(plan, {
|
|
83
|
+
Yes: flags.yes,
|
|
84
|
+
Verbose: flags.verbose,
|
|
85
|
+
NoResume: flags['no-resume'],
|
|
86
|
+
ConfigFile: flags.config,
|
|
87
|
+
Fast: fast,
|
|
88
|
+
});
|
|
89
|
+
this.renderResult(result, fast);
|
|
277
90
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
const command = `wmic LogicalDisk where DeviceID="C:" get FreeSpace`;
|
|
293
|
-
const output = execSync(command).toString();
|
|
294
|
-
const lines = output.trim().split('\n');
|
|
295
|
-
freeSpace = parseInt(lines[1].trim());
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Event wiring — bridges engine events to CLI output + inquirer prompts
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
wireEventHandlers(engine, verbose) {
|
|
95
|
+
engine.On('prompt', (event) => {
|
|
96
|
+
this.handlePromptEvent(event);
|
|
97
|
+
});
|
|
98
|
+
engine.On('phase:start', (event) => {
|
|
99
|
+
this.log(chalk.cyan(`\u25b8 ${event.Description}`));
|
|
100
|
+
});
|
|
101
|
+
engine.On('phase:end', (event) => {
|
|
102
|
+
const duration = chalk.dim(`(${this.formatDuration(event.DurationMs)})`);
|
|
103
|
+
if (event.Status === 'completed') {
|
|
104
|
+
this.log(chalk.green(` \u2713 ${event.Phase} completed ${duration}`));
|
|
296
105
|
}
|
|
297
|
-
else {
|
|
298
|
-
|
|
299
|
-
const command = `df -k / | tail -1 | awk '{ print $4; }'`;
|
|
300
|
-
freeSpace = parseInt(execSync(command).toString().trim()) * 1024;
|
|
106
|
+
else if (event.Status === 'failed') {
|
|
107
|
+
this.log(chalk.red(` \u2717 ${event.Phase} failed ${duration}`));
|
|
301
108
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
109
|
+
});
|
|
110
|
+
engine.On('step:progress', (event) => {
|
|
111
|
+
if (verbose) {
|
|
112
|
+
const percent = event.Percent != null ? ` (${event.Percent}%)` : '';
|
|
113
|
+
this.log(chalk.dim(` ${event.Message}${percent}`));
|
|
305
114
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
115
|
+
});
|
|
116
|
+
engine.On('log', (event) => {
|
|
117
|
+
if (event.Level === 'verbose' && !verbose)
|
|
118
|
+
return;
|
|
119
|
+
this.log(event.Message);
|
|
120
|
+
});
|
|
121
|
+
engine.On('warn', (event) => {
|
|
122
|
+
this.log(chalk.yellow(` \u26a0 ${event.Message}`));
|
|
123
|
+
});
|
|
124
|
+
engine.On('error', (event) => {
|
|
125
|
+
this.log(chalk.red(` \u2717 [${event.Phase}] ${event.Error.message}`));
|
|
126
|
+
if (event.Error.SuggestedFix) {
|
|
127
|
+
this.log(chalk.yellow(` \u2192 ${event.Error.SuggestedFix}`));
|
|
310
128
|
}
|
|
311
|
-
}
|
|
312
|
-
catch (error) {
|
|
313
|
-
this.logToStderr(this.toErrorJson(error));
|
|
314
|
-
this.error('Error checking disk space', { exit: 1 });
|
|
315
|
-
}
|
|
129
|
+
});
|
|
316
130
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.log(`Updating ${file}`);
|
|
334
|
-
}
|
|
335
|
-
const filePath = path.join(dirPath, file);
|
|
336
|
-
const data = await fs.readFile(filePath, 'utf8');
|
|
337
|
-
// Replace the values in the file.
|
|
338
|
-
let updatedData = data;
|
|
339
|
-
Object.entries(config).forEach(([key, value = '']) => {
|
|
340
|
-
const regex = new RegExp(`(["\']?${key}["\']?:\\s*["\'])([^"\']*)(["\'])`, 'g');
|
|
341
|
-
const escapedValue = value.replaceAll('$', () => '$$');
|
|
342
|
-
updatedData = updatedData.replace(regex, `$1${escapedValue}$3`);
|
|
131
|
+
async handlePromptEvent(event) {
|
|
132
|
+
let answer;
|
|
133
|
+
switch (event.PromptType) {
|
|
134
|
+
case 'select':
|
|
135
|
+
answer = await select({
|
|
136
|
+
message: event.Message,
|
|
137
|
+
choices: (event.Choices ?? []).map((c) => ({
|
|
138
|
+
name: c.Label,
|
|
139
|
+
value: c.Value,
|
|
140
|
+
})),
|
|
141
|
+
});
|
|
142
|
+
break;
|
|
143
|
+
case 'confirm': {
|
|
144
|
+
const confirmed = await confirm({
|
|
145
|
+
message: event.Message,
|
|
146
|
+
default: event.Default === 'yes' || event.Default === 'true',
|
|
343
147
|
});
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
this.log(`Updated ${file}`);
|
|
148
|
+
answer = confirmed ? 'yes' : 'no';
|
|
149
|
+
break;
|
|
347
150
|
}
|
|
151
|
+
case 'input':
|
|
152
|
+
answer = await input({
|
|
153
|
+
message: event.Message,
|
|
154
|
+
default: event.Default,
|
|
155
|
+
});
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
answer = event.Default ?? '';
|
|
159
|
+
break;
|
|
348
160
|
}
|
|
349
|
-
|
|
350
|
-
console.error('Error:', err);
|
|
351
|
-
}
|
|
161
|
+
event.Resolve(answer);
|
|
352
162
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Rendering helpers
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
renderHeader() {
|
|
167
|
+
this.log('');
|
|
168
|
+
this.log(chalk.bold('MemberJunction Installer'));
|
|
169
|
+
this.log('\u2500'.repeat(23));
|
|
170
|
+
this.log('');
|
|
171
|
+
}
|
|
172
|
+
renderDryRun(plan) {
|
|
173
|
+
this.log('');
|
|
174
|
+
this.log(chalk.bold(plan.Summarize()));
|
|
175
|
+
this.log('');
|
|
176
|
+
this.log(chalk.dim('Run without --dry-run to execute this plan.'));
|
|
177
|
+
}
|
|
178
|
+
renderResult(result, fast = false) {
|
|
179
|
+
this.log('');
|
|
180
|
+
if (result.Success) {
|
|
181
|
+
this.log(chalk.green.bold('Installation completed successfully!'));
|
|
182
|
+
if (result.Warnings.length > 0) {
|
|
183
|
+
this.log(chalk.yellow(` ${result.Warnings.length} warning(s) during install.`));
|
|
184
|
+
}
|
|
185
|
+
this.log(chalk.dim(` Duration: ${this.formatDuration(result.DurationMs)}`));
|
|
186
|
+
this.log(chalk.dim(` Phases: ${result.PhasesCompleted.join(', ')}`));
|
|
187
|
+
if (fast) {
|
|
188
|
+
this.log('');
|
|
189
|
+
this.log(chalk.cyan(' Fast mode was used. Smoke test was skipped and some post-codegen'));
|
|
190
|
+
this.log(chalk.cyan(' rebuild steps were optimized. If you encounter runtime errors,'));
|
|
191
|
+
this.log(chalk.cyan(' re-run without --fast for a full install.'));
|
|
192
|
+
}
|
|
359
193
|
return;
|
|
360
194
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
this.log(`
|
|
364
|
-
}
|
|
365
|
-
catch (err) {
|
|
366
|
-
this.logToStderr(`An error occurred while renaming the '${oldFolderPath}' folder:`, err);
|
|
195
|
+
this.log(chalk.red.bold('Installation failed.'));
|
|
196
|
+
if (result.PhasesFailed.length > 0) {
|
|
197
|
+
this.log(chalk.red(` Failed phase(s): ${result.PhasesFailed.join(', ')}`));
|
|
367
198
|
}
|
|
199
|
+
this.log(chalk.dim(` Duration: ${this.formatDuration(result.DurationMs)}`));
|
|
200
|
+
this.log('');
|
|
201
|
+
this.log(chalk.yellow('Run "mj install" to resume from the last checkpoint.'));
|
|
202
|
+
this.log(chalk.yellow('Run "mj install --no-resume" to start fresh.'));
|
|
203
|
+
this.log(chalk.yellow('Run "mj doctor" to diagnose issues.'));
|
|
204
|
+
this.error('Installation failed', { exit: 1 });
|
|
368
205
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// Read the mj.config.cjs file
|
|
379
|
-
const configFileContent = await fs.readFile('mj.config.cjs', 'utf8');
|
|
380
|
-
// Parse the content into an AST
|
|
381
|
-
const ast = recast.parse(configFileContent);
|
|
382
|
-
// Modify the AST
|
|
383
|
-
const n = recast.types.namedTypes;
|
|
384
|
-
const b = recast.types.builders;
|
|
385
|
-
recast.types.visit(ast, {
|
|
386
|
-
visitObjectExpression(path) {
|
|
387
|
-
const properties = path.node.properties;
|
|
388
|
-
// Check if newUserSetup key exists
|
|
389
|
-
const newUserSetupProperty = properties.find((prop) => n.Property.check(prop) && n.Identifier.check(prop.key) && prop.key.name === 'newUserSetup');
|
|
390
|
-
const newUserSetupValue = b.objectExpression([
|
|
391
|
-
b.property('init', b.identifier('userName'), b.literal(userName || '')),
|
|
392
|
-
b.property('init', b.identifier('firstName'), b.literal(firstName || '')),
|
|
393
|
-
b.property('init', b.identifier('lastName'), b.literal(lastName || '')),
|
|
394
|
-
b.property('init', b.identifier('email'), b.literal(email || '')),
|
|
395
|
-
]);
|
|
396
|
-
if (newUserSetupProperty && newUserSetupProperty.type === 'Property') {
|
|
397
|
-
// Overwrite the existing newUserSetup key with an object
|
|
398
|
-
newUserSetupProperty.value = newUserSetupValue;
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
// Add a new newUserSetup key
|
|
402
|
-
properties.push(b.property('init', b.identifier('newUserSetup'), newUserSetupValue));
|
|
403
|
-
}
|
|
404
|
-
return false; // Stop traversing this path
|
|
405
|
-
},
|
|
406
|
-
});
|
|
407
|
-
// Serialize the AST back to a string
|
|
408
|
-
const updatedConfigFileContent = recast.prettyPrint(ast).code;
|
|
409
|
-
// Write the updated content back to the file
|
|
410
|
-
await fs.writeFile('mj.config.cjs', updatedConfigFileContent);
|
|
411
|
-
this.log(` Updated mj.config.cjs`);
|
|
412
|
-
}
|
|
413
|
-
catch (err) {
|
|
414
|
-
this.logToStderr('Error:', err);
|
|
415
|
-
}
|
|
206
|
+
formatDuration(ms) {
|
|
207
|
+
if (ms < 1000)
|
|
208
|
+
return `${ms}ms`;
|
|
209
|
+
const seconds = Math.floor(ms / 1000);
|
|
210
|
+
if (seconds < 60)
|
|
211
|
+
return `${seconds}s`;
|
|
212
|
+
const minutes = Math.floor(seconds / 60);
|
|
213
|
+
const remainingSeconds = seconds % 60;
|
|
214
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
416
215
|
}
|
|
417
216
|
}
|
|
418
217
|
//# sourceMappingURL=index.js.map
|