@reldens/cms 0.27.0 → 0.29.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/lib/installer.js CHANGED
@@ -1,497 +1,636 @@
1
- /**
2
- *
3
- * Reldens - CMS - Installer
4
- *
5
- */
6
-
7
- const { FileHandler, Encryptor } = require('@reldens/server-utils');
8
- const { DriversMap, DriversClassMap, EntitiesGenerator, PrismaSchemaGenerator } = require('@reldens/storage');
9
- const { EntitiesLoader } = require('./entities-loader');
10
- const { Logger, sc } = require('@reldens/utils');
11
-
12
- class Installer
13
- {
14
-
15
- constructor(props)
16
- {
17
- this.app = sc.get(props, 'app', false);
18
- this.appServer = sc.get(props, 'appServer', false);
19
- this.appServerFactory = sc.get(props, 'appServerFactory', false);
20
- this.renderEngine = sc.get(props, 'renderEngine', false);
21
- this.projectRoot = sc.get(props, 'projectRoot', './');
22
- this.projectTemplatesPath = FileHandler.joinPaths(this.projectRoot, 'templates');
23
- this.projectPublicPath = FileHandler.joinPaths(this.projectRoot, 'public');
24
- this.projectPublicAssetsPath = FileHandler.joinPaths(this.projectPublicPath, 'assets');
25
- this.projectCssPath = FileHandler.joinPaths(this.projectPublicPath, 'css');
26
- this.projectJsPath = FileHandler.joinPaths(this.projectPublicPath, 'js');
27
- this.installLockPath = FileHandler.joinPaths(this.projectRoot, 'install.lock');
28
- this.envFilePath = FileHandler.joinPaths(this.projectRoot, '.env');
29
- this.modulePath = FileHandler.joinPaths(__dirname, '..');
30
- this.installerPath = FileHandler.joinPaths(this.modulePath, 'install');
31
- this.migrationsPath = FileHandler.joinPaths(this.modulePath, 'migrations');
32
- this.defaultTemplatesPath = FileHandler.joinPaths(this.modulePath, 'templates');
33
- this.moduleAdminPath = FileHandler.joinPaths(this.modulePath, 'admin');
34
- this.moduleAdminAssetsPath = FileHandler.joinPaths(this.moduleAdminPath, 'assets');
35
- this.moduleAdminTemplatesPath = FileHandler.joinPaths(this.moduleAdminPath, 'templates')
36
- this.indexTemplatePath = FileHandler.joinPaths(this.defaultTemplatesPath, 'index.js.dist');
37
- this.postInstallCallback = sc.get(props, 'postInstallCallback', false);
38
- this.prismaClient = sc.get(props, 'prismaClient', false);
39
- this.entitiesLoader = new EntitiesLoader({projectRoot: this.projectRoot});
40
- }
41
-
42
- isInstalled()
43
- {
44
- return FileHandler.exists(this.installLockPath);
45
- }
46
-
47
- async prepareSetup(app, appServer, appServerFactory, renderEngine)
48
- {
49
- if(!app){
50
- Logger.error('Missing app on prepareSetup for Installer.');
51
- return false;
52
- }
53
- if(!appServer){
54
- Logger.error('Missing appServer on prepareSetup for Installer.');
55
- return false;
56
- }
57
- if(!appServerFactory){
58
- Logger.error('Missing appServerFactory on prepareSetup for Installer.');
59
- return false;
60
- }
61
- if(!renderEngine){
62
- Logger.error('Missing renderEngine on prepareSetup for Installer.');
63
- return false;
64
- }
65
- this.app = app;
66
- this.appServer = appServer;
67
- this.appServerFactory = appServerFactory;
68
- this.renderEngine = renderEngine;
69
- app.use('/install-assets', appServerFactory.applicationFramework.static(this.installerPath, {index: false}));
70
- app.use(appServerFactory.session({
71
- secret: Encryptor.generateSecretKey(),
72
- resave: true,
73
- saveUninitialized: true
74
- }));
75
- app.use(async (req, res, next) => {
76
- return await this.executeForEveryRequest(req, res, next);
77
- });
78
- app.post('/install', async (req, res) => {
79
- return await this.executeInstallProcess(req, res);
80
- });
81
- return true;
82
- }
83
-
84
- async executeForEveryRequest(req, res, next)
85
- {
86
- if(this.isInstalled()){
87
- return next();
88
- }
89
- let urlPath = req._parsedUrl.pathname;
90
- if('' === urlPath || '/' === urlPath){
91
- let installerIndexPath = FileHandler.joinPaths(this.installerPath, 'index.html');
92
- if(!FileHandler.exists(installerIndexPath)){
93
- return res.status(500).send('Installer template not found.');
94
- }
95
- let content = FileHandler.readFile(installerIndexPath);
96
- let contentParams = req.session?.templateVariables || this.fetchDefaults();
97
- let errorParam = req.query?.error;
98
- if(errorParam){
99
- contentParams.errorMessage = this.getErrorMessage(errorParam);
100
- }
101
- return res.send(this.renderEngine.render(content, contentParams));
102
- }
103
- if('/install' !== urlPath){
104
- return res.redirect('/');
105
- }
106
- next();
107
- }
108
-
109
- getErrorMessage(errorCode)
110
- {
111
- let errorMessages = {
112
- 'invalid-driver': 'Invalid storage driver selected.',
113
- 'connection-failed': 'Database connection failed. Please check your credentials.',
114
- 'raw-query-not-found': 'Query method not found in driver.',
115
- 'sql-file-not-found': 'SQL installation file not found.',
116
- 'sql-cms-tables-creation-failed': 'Failed to create CMS tables.',
117
- 'sql-user-auth-creation-failed': 'Failed to create user authentication tables.',
118
- 'sql-default-user-error': 'Failed to create default user.',
119
- 'sql-default-homepage-error': 'Failed to create default homepage.',
120
- 'installation-entities-generation-failed': 'Failed to generate entities.',
121
- 'installation-entities-callback-failed': 'Failed to process entities for callback.',
122
- 'configuration-error': 'Configuration error while completing installation.',
123
- 'already-installed': 'The application is already installed.'
124
- };
125
- return sc.get(errorMessages, errorCode, 'An unknown error occurred during installation.');
126
- }
127
-
128
- async executeInstallProcess(req, res)
129
- {
130
- if(this.isInstalled()){
131
- return res.redirect('/?redirect=already-installed');
132
- }
133
- let templateVariables = req.body;
134
- req.session.templateVariables = templateVariables;
135
- let selectedDriver = templateVariables['db-storage-driver'];
136
- let driverClass = DriversMap[selectedDriver];
137
- if(!driverClass){
138
- Logger.error('Invalid storage driver: ' + selectedDriver);
139
- return res.redirect('/?error=invalid-driver');
140
- }
141
- let dbConfig = {
142
- client: templateVariables['db-client'],
143
- config: {
144
- host: templateVariables['db-host'],
145
- port: Number(templateVariables['db-port']),
146
- database: templateVariables['db-name'],
147
- user: templateVariables['db-username'],
148
- password: templateVariables['db-password'],
149
- multipleStatements: true
150
- },
151
- debug: false
152
- };
153
- if('prisma' === selectedDriver && this.prismaClient){
154
- dbConfig.prismaClient = this.prismaClient;
155
- }
156
- let dbDriver = new driverClass(dbConfig);
157
- if(!await dbDriver.connect()){
158
- Logger.error('Connection failed');
159
- return res.redirect('/?error=connection-failed');
160
- }
161
- if(!sc.isObjectFunction(dbDriver, 'rawQuery')){
162
- Logger.error('Method "rawQuery" not found.');
163
- return res.redirect('/?error=raw-query-not-found');
164
- }
165
- let executeFiles = {
166
- 'install-cms-tables': 'install.sql',
167
- 'install-user-auth': 'users-authentication.sql',
168
- 'install-default-user': 'default-user.sql',
169
- 'install-default-homepage': 'default-homepage.sql',
170
- 'install-default-blocks': 'default-blocks.sql',
171
- 'install-entity-access': 'default-entity-access.sql',
172
- 'install-dynamic-forms': 'default-forms.sql'
173
- };
174
- for(let checkboxName of Object.keys(executeFiles)){
175
- let fileName = executeFiles[checkboxName];
176
- let redirectError = await this.executeQueryFile(
177
- sc.get(templateVariables, checkboxName, 'off'),
178
- fileName,
179
- dbDriver
180
- );
181
- if('' !== redirectError){
182
- return res.redirect(redirectError);
183
- }
184
- }
185
- let entitiesGenerationResult = await this.generateEntities(dbDriver, false, true);
186
- if(!entitiesGenerationResult){
187
- Logger.error('Entities generation error.');
188
- return res.redirect('/?error=installation-entities-generation-failed');
189
- }
190
- Logger.info('Generated entities.');
191
- try {
192
- let mappedVariablesForConfig = this.mapVariablesForConfig(templateVariables);
193
- await this.createEnvFile(this.mapVariablesForTemplate(mappedVariablesForConfig));
194
- await this.prepareProjectDirectories();
195
- await this.copyAdminDirectory();
196
- await this.createIndexJsFile(templateVariables);
197
- if(sc.isFunction(this.postInstallCallback)){
198
- if(this.appServer && sc.isFunction(this.appServer.close)){
199
- await this.appServer.close();
200
- }
201
- Logger.debug('Running postInstallCallback.');
202
- let callbackResult = await this.postInstallCallback({
203
- loadedEntities: this.entitiesLoader.loadEntities(selectedDriver),
204
- mappedVariablesForConfig
205
- });
206
- if(false === callbackResult){
207
- Logger.error('Post-install callback failed.');
208
- return res.redirect('/?error=installation-entities-callback-failed');
209
- }
210
- }
211
- await this.createLockFile();
212
- Logger.info('Installation successful!');
213
- let successContent = 'Installation successful! Run "node ." to start your CMS.';
214
- let successFileContent = FileHandler.readFile(FileHandler.joinPaths(this.installerPath, 'success.html'));
215
- if(successFileContent){
216
- successContent = this.renderEngine.render(
217
- successFileContent,
218
- {adminPath: templateVariables['app-admin-path']},
219
- );
220
- }
221
- return res.send(successContent);
222
- } catch (error) {
223
- Logger.critical('Configuration error: '+error.message);
224
- return res.redirect('/?error=installation-error');
225
- }
226
- }
227
-
228
- async executeQueryFile(isMarked, fileName, dbDriver)
229
- {
230
- if('on' !== isMarked){
231
- return '';
232
- }
233
- let sqlFileContent = FileHandler.readFile(FileHandler.joinPaths(this.migrationsPath, fileName));
234
- if(!sqlFileContent){
235
- Logger.error('SQL file "'+fileName+'" not found.');
236
- return '/?error=sql-file-not-found&file-name='+fileName;
237
- }
238
- let queryExecutionResult = await dbDriver.rawQuery(sqlFileContent);
239
- if(!queryExecutionResult){
240
- Logger.error('SQL file "'+fileName+'" raw execution failed.');
241
- return '/?error=sql-file-execution-error&file-name='+fileName;
242
- }
243
- Logger.info('SQL file "'+fileName+'" raw execution successfully.');
244
- return '';
245
- }
246
-
247
- async generateEntities(server, isOverride = false, isInstallationMode = false, isDryPrisma = false)
248
- {
249
- let driverType = sc.get(DriversClassMap, server.constructor.name, '');
250
- Logger.debug('Driver type detected: '+driverType+', Server constructor: '+server.constructor.name);
251
- if('prisma' === driverType && !isInstallationMode && !isDryPrisma){
252
- Logger.info('Running prisma introspect "npx prisma db pull"...');
253
- let dbConfig = this.extractDbConfigFromServer(server);
254
- Logger.debug('Extracted DB config:', dbConfig);
255
- if(dbConfig){
256
- let generatedPrismaSchema = await this.generatePrismaSchema(dbConfig);
257
- if(!generatedPrismaSchema){
258
- Logger.error('Prisma schema generation failed.');
259
- return false;
260
- }
261
- Logger.info('Generated Prisma schema for entities generation.');
262
- }
263
- }
264
- if('prisma' === driverType && isDryPrisma){
265
- Logger.info('Skipping Prisma schema generation due to --dry-prisma flag.');
266
- }
267
- let generatorConfig = {
268
- server,
269
- projectPath: this.projectRoot,
270
- isOverride
271
- };
272
- if('prisma' === driverType && this.prismaClient){
273
- generatorConfig.prismaClient = this.prismaClient;
274
- }
275
- let generator = new EntitiesGenerator(generatorConfig);
276
- let success = await generator.generate();
277
- if(!success){
278
- Logger.error('Entities generation failed.');
279
- return false;
280
- }
281
- return true;
282
- }
283
-
284
- extractDbConfigFromServer(server)
285
- {
286
- let config = sc.get(server, 'config');
287
- if(!config){
288
- Logger.warning('Could not extract database config from server.');
289
- return false;
290
- }
291
- let dbConfig = {
292
- client: sc.get(server, 'client', 'mysql'),
293
- config: {
294
- host: sc.get(config, 'host', 'localhost'),
295
- port: sc.get(config, 'port', 3306),
296
- database: sc.get(config, 'database', ''),
297
- user: sc.get(config, 'user', ''),
298
- password: sc.get(config, 'password', ''),
299
- multipleStatements: true
300
- },
301
- debug: false
302
- };
303
- Logger.debug('Extracted DB config structure:', {
304
- client: dbConfig.client,
305
- host: dbConfig.config.host,
306
- database: dbConfig.config.database
307
- });
308
- return dbConfig;
309
- }
310
-
311
- async generatePrismaSchema(connectionData, useDataProxy = false)
312
- {
313
- if(!connectionData){
314
- Logger.error('Missing "connectionData" to generate Prisma Schema.');
315
- return false;
316
- }
317
- let generator = new PrismaSchemaGenerator({
318
- ...connectionData,
319
- dataProxy: useDataProxy,
320
- clientOutputPath: FileHandler.joinPaths(this.projectRoot, 'prisma', 'client'),
321
- prismaSchemaPath: FileHandler.joinPaths(this.projectRoot, 'prisma')
322
- });
323
- let success = await generator.generate();
324
- if(!success){
325
- Logger.error('Prisma schema generation failed.');
326
- return false;
327
- }
328
- return true;
329
- }
330
-
331
- async createEnvFile(templateVariables)
332
- {
333
- let envTemplatePath = FileHandler.joinPaths(this.defaultTemplatesPath, '.env.dist');
334
- let envTemplateContent = FileHandler.readFile(envTemplatePath);
335
- if(!envTemplateContent){
336
- Logger.error('Template ".env.dist" not found: '+envTemplatePath);
337
- return false;
338
- }
339
- return FileHandler.writeFile(this.envFilePath, this.renderEngine.render(envTemplateContent, templateVariables));
340
- }
341
-
342
- mapVariablesForTemplate(configVariables)
343
- {
344
- return {
345
- host: configVariables.host,
346
- port: configVariables.port,
347
- adminPath: configVariables.adminPath,
348
- adminSecret: configVariables.adminSecret,
349
- dbClient: configVariables.database.client,
350
- dbHost: configVariables.database.host,
351
- dbPort: configVariables.database.port,
352
- dbName: configVariables.database.name,
353
- dbUser: configVariables.database.user,
354
- dbPassword: configVariables.database.password,
355
- dbDriver: configVariables.database.driver
356
- };
357
- }
358
-
359
- mapVariablesForConfig(templateVariables)
360
- {
361
- return {
362
- host: sc.get(templateVariables, 'app-host', 'http://localhost'),
363
- port: Number(sc.get(templateVariables, 'app-port', 8080)),
364
- adminPath: sc.get(templateVariables, 'app-admin-path', '/reldens-admin'),
365
- adminSecret: sc.get(templateVariables, 'app-admin-secret', Encryptor.generateSecretKey()),
366
- database: {
367
- client: sc.get(templateVariables, 'db-client', 'mysql'),
368
- host: sc.get(templateVariables, 'db-host', 'localhost'),
369
- port: Number(sc.get(templateVariables, 'db-port', 3306)),
370
- name: sc.get(templateVariables, 'db-name', 'reldens_cms'),
371
- user: sc.get(templateVariables, 'db-username', ''),
372
- password: sc.get(templateVariables, 'db-password', ''),
373
- driver: sc.get(templateVariables, 'db-storage-driver', 'prisma')
374
- }
375
- };
376
- }
377
-
378
- async createIndexJsFile(templateVariables)
379
- {
380
- if(!FileHandler.exists(this.indexTemplatePath)){
381
- Logger.error('Index.js template not found: ' + this.indexTemplatePath);
382
- return false;
383
- }
384
- let indexTemplate = FileHandler.readFile(this.indexTemplatePath);
385
- let driverKey = templateVariables['db-storage-driver'];
386
- let templateParams = {driverKey};
387
- if('prisma' === driverKey){
388
- let prismaClientPath = FileHandler.joinPaths(this.projectRoot, 'prisma', 'client');
389
- templateParams.prismaClientImports = 'const { PrismaClient } = require(\'' + prismaClientPath + '\');';
390
- templateParams.prismaClientParam = ',\n prismaClient: new PrismaClient()';
391
- }
392
- if('prisma' !== driverKey){
393
- templateParams.prismaClientImports = '';
394
- templateParams.prismaClientParam = '';
395
- }
396
- let indexContent = this.renderEngine.render(indexTemplate, templateParams);
397
- let indexFilePath = FileHandler.joinPaths(this.projectRoot, 'index.js');
398
- if(FileHandler.exists(indexFilePath)){
399
- Logger.info('Index.js file already exists, the CMS installer will not override the existent one.');
400
- return true;
401
- }
402
- return FileHandler.writeFile(indexFilePath, indexContent);
403
- }
404
-
405
- async createLockFile()
406
- {
407
- return FileHandler.writeFile(this.installLockPath, 'Installation completed on '+new Date().toISOString());
408
- }
409
-
410
- async copyAdminDirectory()
411
- {
412
- let projectAdminPath = FileHandler.joinPaths(this.projectRoot, 'admin');
413
- if(FileHandler.exists(projectAdminPath)){
414
- Logger.info('Admin folder already exists in project root.');
415
- return true;
416
- }
417
- if(!FileHandler.exists(this.moduleAdminPath)){
418
- Logger.error('Admin folder not found in module path: '+this.moduleAdminPath);
419
- return false;
420
- }
421
- let projectAdminTemplates = FileHandler.joinPaths(projectAdminPath, 'templates');
422
- FileHandler.copyFolderSync(this.moduleAdminTemplatesPath, projectAdminTemplates);
423
- FileHandler.copyFolderSync(this.moduleAdminAssetsPath, this.projectPublicAssetsPath);
424
- FileHandler.copyFile(
425
- FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.css'),
426
- FileHandler.joinPaths(this.projectCssPath, 'reldens-admin-client.css'),
427
- );
428
- FileHandler.copyFile(
429
- FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.js'),
430
- FileHandler.joinPaths(this.projectJsPath, 'reldens-admin-client.js'),
431
- );
432
- Logger.info('Admin folder copied to project root.');
433
- return true;
434
- }
435
-
436
- async prepareProjectDirectories()
437
- {
438
- FileHandler.createFolder(this.projectTemplatesPath);
439
- FileHandler.createFolder(FileHandler.joinPaths(this.projectTemplatesPath, 'layouts'));
440
- FileHandler.createFolder(this.projectPublicPath);
441
- FileHandler.createFolder(this.projectPublicAssetsPath);
442
- FileHandler.createFolder(this.projectCssPath);
443
- FileHandler.createFolder(this.projectJsPath);
444
- let baseFiles = [
445
- 'page.html',
446
- '404.html',
447
- 'browserconfig.xml',
448
- 'favicon.ico',
449
- 'site.webmanifest'
450
- ];
451
- for(let fileName of baseFiles){
452
- FileHandler.copyFile(
453
- FileHandler.joinPaths(this.defaultTemplatesPath, fileName),
454
- FileHandler.joinPaths(this.projectTemplatesPath, fileName)
455
- );
456
- }
457
- FileHandler.copyFile(
458
- FileHandler.joinPaths(this.defaultTemplatesPath, 'layouts', 'default.html'),
459
- FileHandler.joinPaths(this.projectTemplatesPath, 'layouts', 'default.html')
460
- );
461
- FileHandler.copyFile(
462
- FileHandler.joinPaths(this.defaultTemplatesPath, 'css', 'styles.css'),
463
- FileHandler.joinPaths(this.projectCssPath, 'styles.css')
464
- );
465
- FileHandler.copyFile(
466
- FileHandler.joinPaths(this.defaultTemplatesPath, 'js', 'scripts.js'),
467
- FileHandler.joinPaths(this.projectJsPath, 'scripts.js')
468
- );
469
- FileHandler.copyFolderSync(
470
- FileHandler.joinPaths(this.defaultTemplatesPath, 'partials'),
471
- FileHandler.joinPaths(this.projectTemplatesPath, 'partials')
472
- );
473
- FileHandler.copyFolderSync(
474
- FileHandler.joinPaths(this.defaultTemplatesPath, 'domains'),
475
- FileHandler.joinPaths(this.projectTemplatesPath, 'domains')
476
- );
477
- return true;
478
- }
479
-
480
- fetchDefaults()
481
- {
482
- return {
483
- 'app-host': process.env.RELDENS_APP_HOST || 'http://localhost',
484
- 'app-port': process.env.RELDENS_APP_PORT || '8080',
485
- 'app-admin-path': process.env.RELDENS_ADMIN_ROUTE_PATH || '/reldens-admin',
486
- 'db-storage-driver': process.env.RELDENS_STORAGE_DRIVER || 'prisma',
487
- 'db-client': process.env.RELDENS_DB_CLIENT || 'mysql',
488
- 'db-host': process.env.RELDENS_DB_HOST || 'localhost',
489
- 'db-port': process.env.RELDENS_DB_PORT || '3306',
490
- 'db-name': process.env.RELDENS_DB_NAME || 'reldens_cms',
491
- 'db-username': process.env.RELDENS_DB_USER || '',
492
- 'db-password': process.env.RELDENS_DB_PASSWORD || ''
493
- };
494
- }
495
- }
496
-
497
- module.exports.Installer = Installer;
1
+ /**
2
+ *
3
+ * Reldens - CMS - Installer
4
+ *
5
+ */
6
+
7
+ const { fork, execSync } = require('child_process');
8
+ const { EntitiesLoader } = require('./entities-loader');
9
+ const { MySQLInstaller } = require('./mysql-installer');
10
+ const { FileHandler, Encryptor } = require('@reldens/server-utils');
11
+ const { DriversMap, DriversClassMap, EntitiesGenerator, PrismaSchemaGenerator } = require('@reldens/storage');
12
+ const { Logger, sc } = require('@reldens/utils');
13
+
14
+ class Installer
15
+ {
16
+
17
+ constructor(props)
18
+ {
19
+ this.app = sc.get(props, 'app', false);
20
+ this.appServer = sc.get(props, 'appServer', false);
21
+ this.appServerFactory = sc.get(props, 'appServerFactory', false);
22
+ this.renderEngine = sc.get(props, 'renderEngine', false);
23
+ this.projectRoot = sc.get(props, 'projectRoot', './');
24
+ this.projectTemplatesPath = FileHandler.joinPaths(this.projectRoot, 'templates');
25
+ this.projectPublicPath = FileHandler.joinPaths(this.projectRoot, 'public');
26
+ this.projectPublicAssetsPath = FileHandler.joinPaths(this.projectPublicPath, 'assets');
27
+ this.projectCssPath = FileHandler.joinPaths(this.projectPublicPath, 'css');
28
+ this.projectJsPath = FileHandler.joinPaths(this.projectPublicPath, 'js');
29
+ this.installLockPath = FileHandler.joinPaths(this.projectRoot, 'install.lock');
30
+ this.envFilePath = FileHandler.joinPaths(this.projectRoot, '.env');
31
+ this.modulePath = FileHandler.joinPaths(__dirname, '..');
32
+ this.installerPath = FileHandler.joinPaths(this.modulePath, 'install');
33
+ this.migrationsPath = FileHandler.joinPaths(this.modulePath, 'migrations');
34
+ this.defaultTemplatesPath = FileHandler.joinPaths(this.modulePath, 'templates');
35
+ this.moduleAdminPath = FileHandler.joinPaths(this.modulePath, 'admin');
36
+ this.moduleAdminAssetsPath = FileHandler.joinPaths(this.moduleAdminPath, 'assets');
37
+ this.moduleAdminTemplatesPath = FileHandler.joinPaths(this.moduleAdminPath, 'templates');
38
+ this.indexTemplatePath = FileHandler.joinPaths(this.defaultTemplatesPath, 'index.js.dist');
39
+ this.postInstallCallback = sc.get(props, 'postInstallCallback', false);
40
+ this.prismaClient = sc.get(props, 'prismaClient', false);
41
+ this.entitiesLoader = new EntitiesLoader({projectRoot: this.projectRoot});
42
+ this.subprocessMaxAttempts = sc.get(props, 'subprocessMaxAttempts', 1800);
43
+ }
44
+
45
+ isInstalled()
46
+ {
47
+ return FileHandler.exists(this.installLockPath);
48
+ }
49
+
50
+ async configureAppServerRoutes(app, appServer, appServerFactory, renderEngine)
51
+ {
52
+ if(!app){
53
+ Logger.error('Missing app on configureAppServerRoutes for Installer.');
54
+ return false;
55
+ }
56
+ if(!appServer){
57
+ Logger.error('Missing appServer on configureAppServerRoutes for Installer.');
58
+ return false;
59
+ }
60
+ if(!appServerFactory){
61
+ Logger.error('Missing appServerFactory on configureAppServerRoutes for Installer.');
62
+ return false;
63
+ }
64
+ if(!renderEngine){
65
+ Logger.error('Missing renderEngine on configureAppServerRoutes for Installer.');
66
+ return false;
67
+ }
68
+ this.app = app;
69
+ this.appServer = appServer;
70
+ this.appServerFactory = appServerFactory;
71
+ this.renderEngine = renderEngine;
72
+ app.use('/install-assets', appServerFactory.applicationFramework.static(this.installerPath, {index: false}));
73
+ app.use(appServerFactory.session({
74
+ secret: Encryptor.generateSecretKey(),
75
+ resave: true,
76
+ saveUninitialized: true
77
+ }));
78
+ app.use(async (req, res, next) => {
79
+ return await this.executeForEveryRequest(req, res, next);
80
+ });
81
+ app.post('/install', async (req, res) => {
82
+ return await this.executeInstallProcess(req, res);
83
+ });
84
+ return true;
85
+ }
86
+
87
+ async executeForEveryRequest(req, res, next)
88
+ {
89
+ if(this.isInstalled()){
90
+ return next();
91
+ }
92
+ let urlPath = req._parsedUrl.pathname;
93
+ if('' === urlPath || '/' === urlPath){
94
+ let installerIndexPath = FileHandler.joinPaths(this.installerPath, 'index.html');
95
+ if(!FileHandler.exists(installerIndexPath)){
96
+ return res.status(500).send('Installer template not found.');
97
+ }
98
+ let content = FileHandler.readFile(installerIndexPath);
99
+ let contentParams = req.session?.templateVariables || this.fetchDefaults();
100
+ let errorParam = req.query?.error;
101
+ if(errorParam){
102
+ contentParams.errorMessage = this.getErrorMessage(errorParam);
103
+ }
104
+ return res.send(this.renderEngine.render(content, contentParams));
105
+ }
106
+ if('/install' !== urlPath){
107
+ return res.redirect('/');
108
+ }
109
+ next();
110
+ }
111
+
112
+ getErrorMessage(errorCode)
113
+ {
114
+ let errorMessages = {
115
+ 'invalid-driver': 'Invalid storage driver selected.',
116
+ 'installation-dependencies-failed': 'Required dependencies failed to install.',
117
+ 'connection-failed': 'Database connection failed. Please check your credentials.',
118
+ 'raw-query-not-found': 'Query method not found in driver.',
119
+ 'sql-file-not-found': 'SQL installation file not found.',
120
+ 'sql-cms-tables-creation-failed': 'Failed to create CMS tables.',
121
+ 'sql-user-auth-creation-failed': 'Failed to create user authentication tables.',
122
+ 'sql-default-user-error': 'Failed to create default user.',
123
+ 'sql-default-homepage-error': 'Failed to create default homepage.',
124
+ 'installation-entities-generation-failed': 'Failed to generate entities.',
125
+ 'installation-entities-callback-failed': 'Failed to process entities for callback.',
126
+ 'configuration-error': 'Configuration error while completing installation.',
127
+ 'prisma-generation-subprocess-failed': 'Prisma generation subprocess failed.',
128
+ 'temporal-mysql-only-supported': 'Only MySQL is supported at installation time.',
129
+ 'already-installed': 'The application is already installed.'
130
+ };
131
+ return sc.get(errorMessages, errorCode, 'An unknown error occurred during installation.');
132
+ }
133
+
134
+ async executeInstallProcess(req, res)
135
+ {
136
+ if(this.isInstalled()){
137
+ return res.redirect('/?redirect=already-installed');
138
+ }
139
+ // map database configuration variables:
140
+ let templateVariables = req.body;
141
+ req.session.templateVariables = templateVariables;
142
+ let selectedDriver = templateVariables['db-storage-driver'];
143
+ let driverClass = DriversMap[selectedDriver];
144
+ if(!driverClass){
145
+ Logger.error('Invalid storage driver: ' + selectedDriver);
146
+ return res.redirect('/?error=invalid-driver');
147
+ }
148
+ let dbConfig = {
149
+ client: sc.get(templateVariables, 'db-client', 'mysql'),
150
+ config: {
151
+ host: templateVariables['db-host'],
152
+ port: Number(templateVariables['db-port']),
153
+ database: templateVariables['db-name'],
154
+ user: templateVariables['db-username'],
155
+ password: templateVariables['db-password'],
156
+ multipleStatements: true
157
+ },
158
+ debug: false
159
+ };
160
+ if(!await this.checkAndInstallPackages(['@reldens/cms'])){
161
+ Logger.error('Required @reldens/cms dependency installation failed.');
162
+ return res.redirect('/?error=installation-dependencies-failed');
163
+ }
164
+ if('prisma' === selectedDriver){
165
+ if(!await this.checkAndInstallPackages(['@prisma/client'])){
166
+ Logger.error('Required @prisma/client dependency installation failed.');
167
+ return res.redirect('/?error=installation-dependencies-failed');
168
+ }
169
+ }
170
+ let dbDriver = new driverClass(dbConfig);
171
+ if(!sc.isObjectFunction(dbDriver, 'rawQuery')){
172
+ Logger.error('Method "rawQuery" not found in driver.', driverClass);
173
+ return res.redirect('/?error=raw-query-not-found');
174
+ }
175
+ let queryFilesResult = await this.executeQueryFiles(selectedDriver, dbDriver, dbConfig, templateVariables);
176
+ if('' !== queryFilesResult){
177
+ Logger.critical('Invalid query result: ' + queryFilesResult);
178
+ if('prisma' === selectedDriver){
179
+ res.redirect('/?'+queryFilesResult);
180
+ res.on('finish', () => { process.exit(); });
181
+ return;
182
+ }
183
+ return res.redirect('/?'+queryFilesResult);
184
+ }
185
+ // @IMPORTANT: if the selectedDriver is 'prisma', then at this point the executeQueryFiles already created the
186
+ // client through the sub-process worker.
187
+ // Search: FileHandler.joinPaths(this.projectRoot, 'prisma', 'client')
188
+ let entitiesGenerationResult = await this.generateEntities(dbDriver, false, true, false, dbConfig);
189
+ if(!entitiesGenerationResult){
190
+ Logger.error('Entities generation error.');
191
+ if('prisma' === selectedDriver){
192
+ res.redirect('/?error=installation-entities-generation-failed');
193
+ res.on('finish', () => { process.exit(); });
194
+ return;
195
+ }
196
+ return res.redirect('/?error=installation-entities-generation-failed');
197
+ }
198
+ Logger.info('Generated entities.');
199
+ try {
200
+ let mappedVariablesForConfig = this.mapVariablesForConfig(templateVariables);
201
+ await this.createEnvFile(this.mapVariablesForTemplate(mappedVariablesForConfig));
202
+ await this.prepareProjectDirectories();
203
+ await this.copyAdminDirectory();
204
+ await this.createIndexJsFile(templateVariables);
205
+ if(sc.isFunction(this.postInstallCallback)){
206
+ if(this.appServer && sc.isFunction(this.appServer.close)){
207
+ // @TODO - CHECK IF THIS SHOULD BE REMOVED.
208
+ await this.appServer.close();
209
+ }
210
+ Logger.debug('Running postInstallCallback.');
211
+ let callbackResult = await this.postInstallCallback({
212
+ loadedEntities: this.entitiesLoader.loadEntities(selectedDriver),
213
+ mappedVariablesForConfig,
214
+ dataServer: dbDriver
215
+ });
216
+ if(false === callbackResult){
217
+ Logger.error('Post-install callback failed.');
218
+ if('prisma' === selectedDriver){
219
+ res.redirect('/?error=installation-entities-callback-failed');
220
+ res.on('finish', () => { process.exit(); });
221
+ return;
222
+ }
223
+ return res.redirect('/?error=installation-entities-callback-failed');
224
+ }
225
+ }
226
+ await this.createLockFile();
227
+ Logger.info('Installation successful!');
228
+ let successContent = 'Installation successful! Run "node ." to start your CMS.';
229
+ let successFileContent = FileHandler.readFile(FileHandler.joinPaths(this.installerPath, 'success.html'));
230
+ if(successFileContent){
231
+ successContent = this.renderEngine.render(
232
+ successFileContent,
233
+ {adminPath: templateVariables['app-admin-path']}
234
+ );
235
+ }
236
+ return res.send(successContent);
237
+ } catch (error) {
238
+ Logger.critical('Configuration error: '+error.message);
239
+ if('prisma' === selectedDriver){
240
+ res.redirect('/?error=configuration-error');
241
+ res.on('finish', () => { process.exit(); });
242
+ return;
243
+ }
244
+ return res.redirect('/?error=configuration-error');
245
+ }
246
+ }
247
+
248
+ async executeQueryFiles(selectedDriver, dbDriver, dbConfig, templateVariables)
249
+ {
250
+ if('prisma' === selectedDriver){
251
+ let subProcessResult = await this.runSubprocessInstallation(dbConfig, templateVariables);
252
+ if(!subProcessResult){
253
+ return 'error=prisma-generation-subprocess-failed';
254
+ }
255
+ return '';
256
+ }
257
+ if(!await dbDriver.connect()){
258
+ Logger.error('Connection failed');
259
+ return 'error=connection-failed';
260
+ }
261
+ if(-1 === dbConfig.client.indexOf('mysql')){
262
+ return 'error=temporal-mysql-only-supported';
263
+ }
264
+ let migrationFiles = MySQLInstaller.migrationFiles();
265
+ for(let checkboxName of Object.keys(migrationFiles)){
266
+ let fileName = migrationFiles[checkboxName];
267
+ let redirectError = await MySQLInstaller.executeQueryFile(
268
+ sc.get(templateVariables, checkboxName, 'off'),
269
+ fileName,
270
+ dbDriver,
271
+ this.migrationsPath
272
+ );
273
+ if('' !== redirectError){
274
+ return redirectError;
275
+ }
276
+ }
277
+ return '';
278
+ }
279
+
280
+ async generateEntities(
281
+ server,
282
+ isOverride = false,
283
+ isInstallationMode = false,
284
+ isDryPrisma = false,
285
+ dbConfig = null
286
+ ){
287
+ let driverType = sc.get(DriversClassMap, server.constructor.name, '');
288
+ Logger.debug('Driver type detected: '+driverType+', Server constructor: '+server.constructor.name);
289
+ if('prisma' === driverType && !isDryPrisma){
290
+ Logger.info('Running prisma introspect "npx prisma db pull"...');
291
+ if(!dbConfig){
292
+ dbConfig = this.extractDbConfigFromServer(server);
293
+ Logger.debug('Extracted DB config.');
294
+ }
295
+ Logger.debug('DB config:', dbConfig);
296
+ if(dbConfig){
297
+ let generatedPrismaSchema = await this.generatePrismaSchema(dbConfig);
298
+ if(!generatedPrismaSchema){
299
+ Logger.error('Prisma schema generation failed.');
300
+ return false;
301
+ }
302
+ Logger.info('Generated Prisma schema for entities generation.');
303
+ if(isInstallationMode){
304
+ Logger.info('Creating local Prisma client for entities generation...');
305
+ let localPrismaClient = await MySQLInstaller.createPrismaClient(this.projectRoot);
306
+ if(localPrismaClient){
307
+ this.prismaClient = localPrismaClient;
308
+ server.prisma = localPrismaClient;
309
+ }
310
+ }
311
+ }
312
+ }
313
+ if('prisma' === driverType && isDryPrisma){
314
+ Logger.info('Skipping Prisma schema generation due to --dry-prisma flag.');
315
+ }
316
+ let generatorConfig = {server, projectPath: this.projectRoot, isOverride};
317
+ if('prisma' === driverType && this.prismaClient){
318
+ generatorConfig.prismaClient = this.prismaClient;
319
+ }
320
+ let generator = new EntitiesGenerator(generatorConfig);
321
+ let success = await generator.generate();
322
+ if(!success){
323
+ Logger.error('Entities generation failed.');
324
+ return false;
325
+ }
326
+ return true;
327
+ }
328
+
329
+ extractDbConfigFromServer(server)
330
+ {
331
+ let config = sc.get(server, 'config');
332
+ if(!config){
333
+ Logger.warning('Could not extract database config from server.');
334
+ return false;
335
+ }
336
+ let dbConfig = {
337
+ client: sc.get(server, 'client', 'mysql'),
338
+ config: {
339
+ host: sc.get(config, 'host', 'localhost'),
340
+ port: sc.get(config, 'port', 3306),
341
+ database: sc.get(config, 'database', ''),
342
+ user: sc.get(config, 'user', ''),
343
+ password: sc.get(config, 'password', ''),
344
+ multipleStatements: true
345
+ },
346
+ debug: false
347
+ };
348
+ Logger.debug('Extracted DB config structure:', {
349
+ client: dbConfig.client,
350
+ host: dbConfig.config.host,
351
+ database: dbConfig.config.database
352
+ });
353
+ return dbConfig;
354
+ }
355
+
356
+ async generatePrismaSchema(connectionData, useDataProxy = false)
357
+ {
358
+ if(!connectionData){
359
+ Logger.error('Missing "connectionData" to generate Prisma Schema.');
360
+ return false;
361
+ }
362
+ let generator = new PrismaSchemaGenerator({
363
+ ...connectionData,
364
+ dataProxy: useDataProxy,
365
+ clientOutputPath: FileHandler.joinPaths(this.projectRoot, 'prisma', 'client'),
366
+ prismaSchemaPath: FileHandler.joinPaths(this.projectRoot, 'prisma')
367
+ });
368
+ let success = await generator.generate();
369
+ if(!success){
370
+ Logger.error('Prisma schema generation failed.');
371
+ return false;
372
+ }
373
+ return true;
374
+ }
375
+
376
+ async createEnvFile(templateVariables)
377
+ {
378
+ let envTemplatePath = FileHandler.joinPaths(this.defaultTemplatesPath, '.env.dist');
379
+ let envTemplateContent = FileHandler.readFile(envTemplatePath);
380
+ if(!envTemplateContent){
381
+ Logger.error('Template ".env.dist" not found: '+envTemplatePath);
382
+ return false;
383
+ }
384
+ return FileHandler.writeFile(this.envFilePath, this.renderEngine.render(envTemplateContent, templateVariables));
385
+ }
386
+
387
+ mapVariablesForTemplate(configVariables)
388
+ {
389
+ return {
390
+ host: configVariables.host,
391
+ port: configVariables.port,
392
+ publicUrl: configVariables.publicUrl,
393
+ adminPath: configVariables.adminPath,
394
+ adminSecret: configVariables.adminSecret,
395
+ dbClient: configVariables.database.client,
396
+ dbHost: configVariables.database.host,
397
+ dbPort: configVariables.database.port,
398
+ dbName: configVariables.database.name,
399
+ dbUser: configVariables.database.user,
400
+ dbPassword: configVariables.database.password,
401
+ dbDriver: configVariables.database.driver
402
+ };
403
+ }
404
+
405
+ mapVariablesForConfig(templateVariables)
406
+ {
407
+ return {
408
+ host: sc.get(templateVariables, 'app-host', 'http://localhost'),
409
+ port: Number(sc.get(templateVariables, 'app-port', 8080)),
410
+ publicUrl: sc.get(templateVariables, 'app-public-url', ''),
411
+ adminPath: sc.get(templateVariables, 'app-admin-path', '/reldens-admin'),
412
+ adminSecret: sc.get(templateVariables, 'app-admin-secret', Encryptor.generateSecretKey()),
413
+ database: {
414
+ client: sc.get(templateVariables, 'db-client', 'mysql'),
415
+ host: sc.get(templateVariables, 'db-host', 'localhost'),
416
+ port: Number(sc.get(templateVariables, 'db-port', 3306)),
417
+ name: sc.get(templateVariables, 'db-name', 'reldens_cms'),
418
+ user: sc.get(templateVariables, 'db-username', ''),
419
+ password: sc.get(templateVariables, 'db-password', ''),
420
+ driver: sc.get(templateVariables, 'db-storage-driver', 'prisma')
421
+ }
422
+ };
423
+ }
424
+
425
+ async createIndexJsFile(templateVariables)
426
+ {
427
+ if(!FileHandler.exists(this.indexTemplatePath)){
428
+ Logger.error('Index.js template not found: ' + this.indexTemplatePath);
429
+ return false;
430
+ }
431
+ let indexTemplate = FileHandler.readFile(this.indexTemplatePath);
432
+ let driverKey = templateVariables['db-storage-driver'];
433
+ let templateParams = {driverKey};
434
+ if('prisma' === driverKey){
435
+ templateParams.prismaClientImports = 'const { PrismaClient } = require(\'./prisma/client\');';
436
+ templateParams.prismaClientParam = ',\n prismaClient: new PrismaClient()';
437
+ }
438
+ if('prisma' !== driverKey){
439
+ templateParams.prismaClientImports = '';
440
+ templateParams.prismaClientParam = '';
441
+ }
442
+ let indexContent = this.renderEngine.render(indexTemplate, templateParams);
443
+ let indexFilePath = FileHandler.joinPaths(this.projectRoot, 'index.js');
444
+ if(FileHandler.exists(indexFilePath)){
445
+ Logger.info('Index.js file already exists, the CMS installer will not override the existent one.');
446
+ return true;
447
+ }
448
+ return FileHandler.writeFile(indexFilePath, indexContent);
449
+ }
450
+
451
+ async createLockFile()
452
+ {
453
+ return FileHandler.writeFile(this.installLockPath, 'Installation completed on '+new Date().toISOString());
454
+ }
455
+
456
+ async copyAdminDirectory()
457
+ {
458
+ let projectAdminPath = FileHandler.joinPaths(this.projectRoot, 'admin');
459
+ if(FileHandler.exists(projectAdminPath)){
460
+ Logger.info('Admin folder already exists in project root.');
461
+ return true;
462
+ }
463
+ if(!FileHandler.exists(this.moduleAdminPath)){
464
+ Logger.error('Admin folder not found in module path: '+this.moduleAdminPath);
465
+ return false;
466
+ }
467
+ let projectAdminTemplates = FileHandler.joinPaths(projectAdminPath, 'templates');
468
+ FileHandler.copyFolderSync(this.moduleAdminTemplatesPath, projectAdminTemplates);
469
+ FileHandler.copyFolderSync(this.moduleAdminAssetsPath, this.projectPublicAssetsPath);
470
+ FileHandler.copyFile(
471
+ FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.css'),
472
+ FileHandler.joinPaths(this.projectCssPath, 'reldens-admin-client.css')
473
+ );
474
+ FileHandler.copyFile(
475
+ FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.js'),
476
+ FileHandler.joinPaths(this.projectJsPath, 'reldens-admin-client.js')
477
+ );
478
+ Logger.info('Admin folder copied to project root.');
479
+ return true;
480
+ }
481
+
482
+ async prepareProjectDirectories()
483
+ {
484
+ FileHandler.createFolder(this.projectTemplatesPath);
485
+ FileHandler.createFolder(FileHandler.joinPaths(this.projectTemplatesPath, 'layouts'));
486
+ FileHandler.createFolder(this.projectPublicPath);
487
+ FileHandler.createFolder(this.projectPublicAssetsPath);
488
+ FileHandler.createFolder(this.projectCssPath);
489
+ FileHandler.createFolder(this.projectJsPath);
490
+ let baseFiles = [
491
+ 'page.html',
492
+ '404.html',
493
+ 'browserconfig.xml',
494
+ 'favicon.ico',
495
+ 'site.webmanifest'
496
+ ];
497
+ for(let fileName of baseFiles){
498
+ FileHandler.copyFile(
499
+ FileHandler.joinPaths(this.defaultTemplatesPath, fileName),
500
+ FileHandler.joinPaths(this.projectTemplatesPath, fileName)
501
+ );
502
+ }
503
+ FileHandler.copyFile(
504
+ FileHandler.joinPaths(this.defaultTemplatesPath, 'layouts', 'default.html'),
505
+ FileHandler.joinPaths(this.projectTemplatesPath, 'layouts', 'default.html')
506
+ );
507
+ FileHandler.copyFolderSync(FileHandler.joinPaths(this.defaultTemplatesPath, 'css'), this.projectCssPath);
508
+ FileHandler.copyFolderSync(FileHandler.joinPaths(this.defaultTemplatesPath, 'js'), this.projectJsPath);
509
+ FileHandler.copyFolderSync(
510
+ FileHandler.joinPaths(this.defaultTemplatesPath, 'partials'),
511
+ FileHandler.joinPaths(this.projectTemplatesPath, 'partials')
512
+ );
513
+ FileHandler.copyFolderSync(
514
+ FileHandler.joinPaths(this.defaultTemplatesPath, 'domains'),
515
+ FileHandler.joinPaths(this.projectTemplatesPath, 'domains')
516
+ );
517
+ FileHandler.copyFolderSync(
518
+ FileHandler.joinPaths(this.defaultTemplatesPath, 'assets'),
519
+ this.projectPublicAssetsPath
520
+ );
521
+ return true;
522
+ }
523
+
524
+ fetchDefaults()
525
+ {
526
+ return {
527
+ 'app-host': process.env.RELDENS_APP_HOST || 'http://localhost',
528
+ 'app-port': process.env.RELDENS_APP_PORT || '8080',
529
+ 'app-public-url': process.env.RELDENS_PUBLIC_URL || '',
530
+ 'app-admin-path': process.env.RELDENS_ADMIN_ROUTE_PATH || '/reldens-admin',
531
+ 'db-storage-driver': process.env.RELDENS_STORAGE_DRIVER || 'prisma',
532
+ 'db-client': process.env.RELDENS_DB_CLIENT || 'mysql',
533
+ 'db-host': process.env.RELDENS_DB_HOST || 'localhost',
534
+ 'db-port': process.env.RELDENS_DB_PORT || '3306',
535
+ 'db-name': process.env.RELDENS_DB_NAME || 'reldens_cms',
536
+ 'db-username': process.env.RELDENS_DB_USER || '',
537
+ 'db-password': process.env.RELDENS_DB_PASSWORD || ''
538
+ };
539
+ }
540
+
541
+ async checkAndInstallPackages(requiredPackages)
542
+ {
543
+ let missingPackages = [];
544
+ for(let packageName of requiredPackages){
545
+ let packagePath = FileHandler.joinPaths(this.projectRoot, 'node_modules', packageName);
546
+ if(!FileHandler.exists(packagePath)){
547
+ missingPackages.push(packageName);
548
+ }
549
+ }
550
+ if(0 === missingPackages.length){
551
+ return true;
552
+ }
553
+ Logger.info('Missing required packages: ' + missingPackages.join(', '));
554
+ Logger.info('These packages are required for the CMS to function properly.');
555
+ Logger.info('Would you like to install them automatically? (This may take a few minutes)');
556
+ Logger.info('Installing packages: npm install ' + missingPackages.join(' '));
557
+ try {
558
+ let installCommand = 'npm install ' + missingPackages.join(' ');
559
+ execSync(installCommand, {stdio: 'inherit', cwd: this.projectRoot});
560
+ Logger.info('Dependencies installed successfully.');
561
+ return true;
562
+ } catch (error) {
563
+ Logger.error('Failed to install dependencies: ' + error.message);
564
+ Logger.error('Please run manually: npm install ' + missingPackages.join(' '));
565
+ return false;
566
+ }
567
+ }
568
+
569
+ async runSubprocessInstallation(dbConfig, templateVariables)
570
+ {
571
+ Logger.info('Subprocess Prisma installation - Starting...');
572
+ let workerPath = FileHandler.joinPaths(__dirname, 'prisma-subprocess-worker.js');
573
+ let worker = fork(workerPath, [], {
574
+ cwd: this.projectRoot,
575
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
576
+ env: {...process.env}
577
+ });
578
+ let message = {
579
+ dbConfig: dbConfig,
580
+ templateVariables: templateVariables,
581
+ migrationsPath: this.migrationsPath,
582
+ projectRoot: this.projectRoot
583
+ };
584
+ worker.stdout.on('data', (data) => {
585
+ Logger.info('Subprocess: '+data.toString().trim());
586
+ });
587
+ worker.stderr.on('data', (data) => {
588
+ Logger.error('Subprocess error: '+data.toString().trim());
589
+ });
590
+ worker.send(message);
591
+ let subprocessCompleted = false;
592
+ let subprocessSuccess = false;
593
+ let workerExited = false;
594
+ worker.on('message', (message) => {
595
+ subprocessCompleted = true;
596
+ subprocessSuccess = sc.get(message, 'success', false);
597
+ if(!subprocessSuccess){
598
+ Logger.error('Subprocess failed: '+sc.get(message, 'error', 'Unknown'));
599
+ }
600
+ });
601
+ worker.on('error', (error) => {
602
+ subprocessCompleted = true;
603
+ subprocessSuccess = false;
604
+ Logger.error('Subprocess error: '+error.message);
605
+ });
606
+ worker.on('exit', (code, signal) => {
607
+ workerExited = true;
608
+ if(!subprocessCompleted){
609
+ subprocessCompleted = true;
610
+ subprocessSuccess = false;
611
+ }
612
+ });
613
+ let attempts = 0;
614
+ while(!subprocessCompleted && attempts < this.subprocessMaxAttempts){
615
+ attempts++;
616
+ await this.waitMilliseconds(100);
617
+ }
618
+ if(!workerExited){
619
+ worker.kill('SIGTERM');
620
+ await this.waitMilliseconds(1000);
621
+ if(!workerExited){
622
+ worker.kill('SIGKILL');
623
+ }
624
+ }
625
+ Logger.info('Subprocess Prisma installation - Ended.');
626
+ return subprocessSuccess;
627
+ }
628
+
629
+ async waitMilliseconds(ms)
630
+ {
631
+ return new Promise(resolve => setTimeout(resolve, ms));
632
+ }
633
+
634
+ }
635
+
636
+ module.exports.Installer = Installer;