@reldens/cms 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -13
- package/bin/reldens-cms-generate-entities.js +7 -2
- package/bin/reldens-cms.js +154 -50
- package/install/css/installer.css +1 -0
- package/lib/installer.js +206 -70
- package/lib/manager.js +41 -8
- package/lib/mysql-installer.js +92 -0
- package/lib/prisma-subprocess-worker.js +97 -0
- package/lib/template-engine/date-transformer.js +4 -5
- package/migrations/default-homepage.sql +1 -1
- package/package.json +5 -5
- package/templates/.env.dist +1 -0
- package/templates/index.js.dist +30 -32
- package/templates/js/cookie-consent.js +411 -0
package/lib/installer.js
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const { fork, execSync } = require('child_process');
|
|
8
|
+
const { EntitiesLoader } = require('./entities-loader');
|
|
9
|
+
const { MySQLInstaller } = require('./mysql-installer');
|
|
7
10
|
const { FileHandler, Encryptor } = require('@reldens/server-utils');
|
|
8
11
|
const { DriversMap, DriversClassMap, EntitiesGenerator, PrismaSchemaGenerator } = require('@reldens/storage');
|
|
9
|
-
const { EntitiesLoader } = require('./entities-loader');
|
|
10
12
|
const { Logger, sc } = require('@reldens/utils');
|
|
11
13
|
|
|
12
14
|
class Installer
|
|
@@ -32,11 +34,12 @@ class Installer
|
|
|
32
34
|
this.defaultTemplatesPath = FileHandler.joinPaths(this.modulePath, 'templates');
|
|
33
35
|
this.moduleAdminPath = FileHandler.joinPaths(this.modulePath, 'admin');
|
|
34
36
|
this.moduleAdminAssetsPath = FileHandler.joinPaths(this.moduleAdminPath, 'assets');
|
|
35
|
-
this.moduleAdminTemplatesPath = FileHandler.joinPaths(this.moduleAdminPath, 'templates')
|
|
37
|
+
this.moduleAdminTemplatesPath = FileHandler.joinPaths(this.moduleAdminPath, 'templates');
|
|
36
38
|
this.indexTemplatePath = FileHandler.joinPaths(this.defaultTemplatesPath, 'index.js.dist');
|
|
37
39
|
this.postInstallCallback = sc.get(props, 'postInstallCallback', false);
|
|
38
40
|
this.prismaClient = sc.get(props, 'prismaClient', false);
|
|
39
41
|
this.entitiesLoader = new EntitiesLoader({projectRoot: this.projectRoot});
|
|
42
|
+
this.subprocessMaxAttempts = sc.get(props, 'subprocessMaxAttempts', 1800);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
isInstalled()
|
|
@@ -44,22 +47,22 @@ class Installer
|
|
|
44
47
|
return FileHandler.exists(this.installLockPath);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
async
|
|
50
|
+
async configureAppServerRoutes(app, appServer, appServerFactory, renderEngine)
|
|
48
51
|
{
|
|
49
52
|
if(!app){
|
|
50
|
-
Logger.error('Missing app on
|
|
53
|
+
Logger.error('Missing app on configureAppServerRoutes for Installer.');
|
|
51
54
|
return false;
|
|
52
55
|
}
|
|
53
56
|
if(!appServer){
|
|
54
|
-
Logger.error('Missing appServer on
|
|
57
|
+
Logger.error('Missing appServer on configureAppServerRoutes for Installer.');
|
|
55
58
|
return false;
|
|
56
59
|
}
|
|
57
60
|
if(!appServerFactory){
|
|
58
|
-
Logger.error('Missing appServerFactory on
|
|
61
|
+
Logger.error('Missing appServerFactory on configureAppServerRoutes for Installer.');
|
|
59
62
|
return false;
|
|
60
63
|
}
|
|
61
64
|
if(!renderEngine){
|
|
62
|
-
Logger.error('Missing renderEngine on
|
|
65
|
+
Logger.error('Missing renderEngine on configureAppServerRoutes for Installer.');
|
|
63
66
|
return false;
|
|
64
67
|
}
|
|
65
68
|
this.app = app;
|
|
@@ -110,6 +113,7 @@ class Installer
|
|
|
110
113
|
{
|
|
111
114
|
let errorMessages = {
|
|
112
115
|
'invalid-driver': 'Invalid storage driver selected.',
|
|
116
|
+
'installation-dependencies-failed': 'Required dependencies failed to install.',
|
|
113
117
|
'connection-failed': 'Database connection failed. Please check your credentials.',
|
|
114
118
|
'raw-query-not-found': 'Query method not found in driver.',
|
|
115
119
|
'sql-file-not-found': 'SQL installation file not found.',
|
|
@@ -120,6 +124,8 @@ class Installer
|
|
|
120
124
|
'installation-entities-generation-failed': 'Failed to generate entities.',
|
|
121
125
|
'installation-entities-callback-failed': 'Failed to process entities for callback.',
|
|
122
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.',
|
|
123
129
|
'already-installed': 'The application is already installed.'
|
|
124
130
|
};
|
|
125
131
|
return sc.get(errorMessages, errorCode, 'An unknown error occurred during installation.');
|
|
@@ -130,6 +136,7 @@ class Installer
|
|
|
130
136
|
if(this.isInstalled()){
|
|
131
137
|
return res.redirect('/?redirect=already-installed');
|
|
132
138
|
}
|
|
139
|
+
// map database configuration variables:
|
|
133
140
|
let templateVariables = req.body;
|
|
134
141
|
req.session.templateVariables = templateVariables;
|
|
135
142
|
let selectedDriver = templateVariables['db-storage-driver'];
|
|
@@ -139,7 +146,7 @@ class Installer
|
|
|
139
146
|
return res.redirect('/?error=invalid-driver');
|
|
140
147
|
}
|
|
141
148
|
let dbConfig = {
|
|
142
|
-
client: templateVariables
|
|
149
|
+
client: sc.get(templateVariables, 'db-client', 'mysql'),
|
|
143
150
|
config: {
|
|
144
151
|
host: templateVariables['db-host'],
|
|
145
152
|
port: Number(templateVariables['db-port']),
|
|
@@ -150,41 +157,42 @@ class Installer
|
|
|
150
157
|
},
|
|
151
158
|
debug: false
|
|
152
159
|
};
|
|
153
|
-
if(
|
|
154
|
-
|
|
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
|
+
}
|
|
155
169
|
}
|
|
156
170
|
let dbDriver = new driverClass(dbConfig);
|
|
157
|
-
if(!await dbDriver.connect()){
|
|
158
|
-
Logger.error('Connection failed');
|
|
159
|
-
return res.redirect('/?error=connection-failed');
|
|
160
|
-
}
|
|
161
171
|
if(!sc.isObjectFunction(dbDriver, 'rawQuery')){
|
|
162
|
-
Logger.error('Method "rawQuery" not found.');
|
|
172
|
+
Logger.error('Method "rawQuery" not found in driver.', driverClass);
|
|
163
173
|
return res.redirect('/?error=raw-query-not-found');
|
|
164
174
|
}
|
|
165
|
-
let
|
|
166
|
-
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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);
|
|
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;
|
|
183
182
|
}
|
|
183
|
+
return res.redirect('/?'+queryFilesResult);
|
|
184
184
|
}
|
|
185
|
-
|
|
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);
|
|
186
189
|
if(!entitiesGenerationResult){
|
|
187
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
|
+
}
|
|
188
196
|
return res.redirect('/?error=installation-entities-generation-failed');
|
|
189
197
|
}
|
|
190
198
|
Logger.info('Generated entities.');
|
|
@@ -196,15 +204,22 @@ class Installer
|
|
|
196
204
|
await this.createIndexJsFile(templateVariables);
|
|
197
205
|
if(sc.isFunction(this.postInstallCallback)){
|
|
198
206
|
if(this.appServer && sc.isFunction(this.appServer.close)){
|
|
207
|
+
// @TODO - CHECK IF THIS SHOULD BE REMOVED.
|
|
199
208
|
await this.appServer.close();
|
|
200
209
|
}
|
|
201
210
|
Logger.debug('Running postInstallCallback.');
|
|
202
211
|
let callbackResult = await this.postInstallCallback({
|
|
203
212
|
loadedEntities: this.entitiesLoader.loadEntities(selectedDriver),
|
|
204
|
-
mappedVariablesForConfig
|
|
213
|
+
mappedVariablesForConfig,
|
|
214
|
+
dataServer: dbDriver
|
|
205
215
|
});
|
|
206
216
|
if(false === callbackResult){
|
|
207
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
|
+
}
|
|
208
223
|
return res.redirect('/?error=installation-entities-callback-failed');
|
|
209
224
|
}
|
|
210
225
|
}
|
|
@@ -215,43 +230,69 @@ class Installer
|
|
|
215
230
|
if(successFileContent){
|
|
216
231
|
successContent = this.renderEngine.render(
|
|
217
232
|
successFileContent,
|
|
218
|
-
{adminPath: templateVariables['app-admin-path']}
|
|
233
|
+
{adminPath: templateVariables['app-admin-path']}
|
|
219
234
|
);
|
|
220
235
|
}
|
|
221
236
|
return res.send(successContent);
|
|
222
237
|
} catch (error) {
|
|
223
238
|
Logger.critical('Configuration error: '+error.message);
|
|
224
|
-
|
|
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');
|
|
225
245
|
}
|
|
226
246
|
}
|
|
227
247
|
|
|
228
|
-
async
|
|
248
|
+
async executeQueryFiles(selectedDriver, dbDriver, dbConfig, templateVariables)
|
|
229
249
|
{
|
|
230
|
-
if('
|
|
250
|
+
if('prisma' === selectedDriver){
|
|
251
|
+
let subProcessResult = await this.runSubprocessInstallation(dbConfig, templateVariables);
|
|
252
|
+
if(!subProcessResult){
|
|
253
|
+
return 'error=prisma-generation-subprocess-failed';
|
|
254
|
+
}
|
|
231
255
|
return '';
|
|
232
256
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return '/?error=sql-file-not-found&file-name='+fileName;
|
|
257
|
+
if(!await dbDriver.connect()){
|
|
258
|
+
Logger.error('Connection failed');
|
|
259
|
+
return 'error=connection-failed';
|
|
237
260
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
}
|
|
242
276
|
}
|
|
243
|
-
Logger.info('SQL file "'+fileName+'" raw execution successfully.');
|
|
244
277
|
return '';
|
|
245
278
|
}
|
|
246
279
|
|
|
247
|
-
async generateEntities(
|
|
248
|
-
|
|
280
|
+
async generateEntities(
|
|
281
|
+
server,
|
|
282
|
+
isOverride = false,
|
|
283
|
+
isInstallationMode = false,
|
|
284
|
+
isDryPrisma = false,
|
|
285
|
+
dbConfig = null
|
|
286
|
+
){
|
|
249
287
|
let driverType = sc.get(DriversClassMap, server.constructor.name, '');
|
|
250
288
|
Logger.debug('Driver type detected: '+driverType+', Server constructor: '+server.constructor.name);
|
|
251
|
-
if('prisma' === driverType && !
|
|
289
|
+
if('prisma' === driverType && !isDryPrisma){
|
|
252
290
|
Logger.info('Running prisma introspect "npx prisma db pull"...');
|
|
253
|
-
|
|
254
|
-
|
|
291
|
+
if(!dbConfig){
|
|
292
|
+
dbConfig = this.extractDbConfigFromServer(server);
|
|
293
|
+
Logger.debug('Extracted DB config.');
|
|
294
|
+
}
|
|
295
|
+
Logger.debug('DB config:', dbConfig);
|
|
255
296
|
if(dbConfig){
|
|
256
297
|
let generatedPrismaSchema = await this.generatePrismaSchema(dbConfig);
|
|
257
298
|
if(!generatedPrismaSchema){
|
|
@@ -259,16 +300,20 @@ class Installer
|
|
|
259
300
|
return false;
|
|
260
301
|
}
|
|
261
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
|
+
}
|
|
262
311
|
}
|
|
263
312
|
}
|
|
264
313
|
if('prisma' === driverType && isDryPrisma){
|
|
265
314
|
Logger.info('Skipping Prisma schema generation due to --dry-prisma flag.');
|
|
266
315
|
}
|
|
267
|
-
let generatorConfig = {
|
|
268
|
-
server,
|
|
269
|
-
projectPath: this.projectRoot,
|
|
270
|
-
isOverride
|
|
271
|
-
};
|
|
316
|
+
let generatorConfig = {server, projectPath: this.projectRoot, isOverride};
|
|
272
317
|
if('prisma' === driverType && this.prismaClient){
|
|
273
318
|
generatorConfig.prismaClient = this.prismaClient;
|
|
274
319
|
}
|
|
@@ -387,8 +432,7 @@ class Installer
|
|
|
387
432
|
let driverKey = templateVariables['db-storage-driver'];
|
|
388
433
|
let templateParams = {driverKey};
|
|
389
434
|
if('prisma' === driverKey){
|
|
390
|
-
|
|
391
|
-
templateParams.prismaClientImports = 'const { PrismaClient } = require(\'' + prismaClientPath + '\');';
|
|
435
|
+
templateParams.prismaClientImports = 'const { PrismaClient } = require(\'./prisma/client\');';
|
|
392
436
|
templateParams.prismaClientParam = ',\n prismaClient: new PrismaClient()';
|
|
393
437
|
}
|
|
394
438
|
if('prisma' !== driverKey){
|
|
@@ -425,11 +469,11 @@ class Installer
|
|
|
425
469
|
FileHandler.copyFolderSync(this.moduleAdminAssetsPath, this.projectPublicAssetsPath);
|
|
426
470
|
FileHandler.copyFile(
|
|
427
471
|
FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.css'),
|
|
428
|
-
FileHandler.joinPaths(this.projectCssPath, 'reldens-admin-client.css')
|
|
472
|
+
FileHandler.joinPaths(this.projectCssPath, 'reldens-admin-client.css')
|
|
429
473
|
);
|
|
430
474
|
FileHandler.copyFile(
|
|
431
475
|
FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.js'),
|
|
432
|
-
FileHandler.joinPaths(this.projectJsPath, 'reldens-admin-client.js')
|
|
476
|
+
FileHandler.joinPaths(this.projectJsPath, 'reldens-admin-client.js')
|
|
433
477
|
);
|
|
434
478
|
Logger.info('Admin folder copied to project root.');
|
|
435
479
|
return true;
|
|
@@ -460,14 +504,8 @@ class Installer
|
|
|
460
504
|
FileHandler.joinPaths(this.defaultTemplatesPath, 'layouts', 'default.html'),
|
|
461
505
|
FileHandler.joinPaths(this.projectTemplatesPath, 'layouts', 'default.html')
|
|
462
506
|
);
|
|
463
|
-
FileHandler.
|
|
464
|
-
|
|
465
|
-
FileHandler.joinPaths(this.projectCssPath, 'styles.css')
|
|
466
|
-
);
|
|
467
|
-
FileHandler.copyFile(
|
|
468
|
-
FileHandler.joinPaths(this.defaultTemplatesPath, 'js', 'scripts.js'),
|
|
469
|
-
FileHandler.joinPaths(this.projectJsPath, 'scripts.js')
|
|
470
|
-
);
|
|
507
|
+
FileHandler.copyFolderSync(FileHandler.joinPaths(this.defaultTemplatesPath, 'css'), this.projectCssPath);
|
|
508
|
+
FileHandler.copyFolderSync(FileHandler.joinPaths(this.defaultTemplatesPath, 'js'), this.projectJsPath);
|
|
471
509
|
FileHandler.copyFolderSync(
|
|
472
510
|
FileHandler.joinPaths(this.defaultTemplatesPath, 'partials'),
|
|
473
511
|
FileHandler.joinPaths(this.projectTemplatesPath, 'partials')
|
|
@@ -476,6 +514,10 @@ class Installer
|
|
|
476
514
|
FileHandler.joinPaths(this.defaultTemplatesPath, 'domains'),
|
|
477
515
|
FileHandler.joinPaths(this.projectTemplatesPath, 'domains')
|
|
478
516
|
);
|
|
517
|
+
FileHandler.copyFolderSync(
|
|
518
|
+
FileHandler.joinPaths(this.defaultTemplatesPath, 'assets'),
|
|
519
|
+
this.projectPublicAssetsPath
|
|
520
|
+
);
|
|
479
521
|
return true;
|
|
480
522
|
}
|
|
481
523
|
|
|
@@ -495,6 +537,100 @@ class Installer
|
|
|
495
537
|
'db-password': process.env.RELDENS_DB_PASSWORD || ''
|
|
496
538
|
};
|
|
497
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
|
+
|
|
498
634
|
}
|
|
499
635
|
|
|
500
636
|
module.exports.Installer = Installer;
|
package/lib/manager.js
CHANGED
|
@@ -75,10 +75,9 @@ class Manager
|
|
|
75
75
|
this.frontend = sc.get(props, 'frontend', false);
|
|
76
76
|
this.renderEngine = sc.get(props, 'renderEngine', mustache);
|
|
77
77
|
this.prismaClient = sc.get(props, 'prismaClient', false);
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
81
|
-
this.developmentPorts = sc.get(props, 'developmentPorts', [3000, 8080, 8081]);
|
|
78
|
+
this.developmentPatterns = sc.get(props, 'developmentPatterns', []);
|
|
79
|
+
this.developmentEnvironments = sc.get(props, 'developmentEnvironments', []);
|
|
80
|
+
this.developmentPorts = sc.get(props, 'developmentPorts', []);
|
|
82
81
|
this.developmentMultiplier = sc.get(props, 'developmentMultiplier', 10);
|
|
83
82
|
this.appServerConfig = sc.get(props, 'appServerConfig', {});
|
|
84
83
|
this.developmentExternalDomains = sc.get(props, 'developmentExternalDomains', {});
|
|
@@ -207,7 +206,12 @@ class Manager
|
|
|
207
206
|
}
|
|
208
207
|
if(!this.isInstalled()){
|
|
209
208
|
Logger.info('CMS not installed, preparing setup');
|
|
210
|
-
await this.installer.
|
|
209
|
+
await this.installer.configureAppServerRoutes(
|
|
210
|
+
this.app,
|
|
211
|
+
this.appServer,
|
|
212
|
+
this.appServerFactory,
|
|
213
|
+
this.renderEngine
|
|
214
|
+
);
|
|
211
215
|
if(!this.useProvidedServer){
|
|
212
216
|
await this.appServer.listen(this.config.port);
|
|
213
217
|
}
|
|
@@ -226,9 +230,14 @@ class Manager
|
|
|
226
230
|
|
|
227
231
|
buildAppServerConfiguration()
|
|
228
232
|
{
|
|
233
|
+
let useHelmet = true;
|
|
234
|
+
if(!this.isInstalled()){
|
|
235
|
+
useHelmet = false;
|
|
236
|
+
}
|
|
229
237
|
let baseConfig = {
|
|
230
238
|
port: this.config.port,
|
|
231
|
-
useHttps: this.
|
|
239
|
+
useHttps: this.determineHttpsUsage(),
|
|
240
|
+
useHelmet,
|
|
232
241
|
domainMapping: this.domainMapping || {},
|
|
233
242
|
defaultDomain: this.defaultDomain,
|
|
234
243
|
developmentPatterns: this.developmentPatterns,
|
|
@@ -248,14 +257,37 @@ class Manager
|
|
|
248
257
|
return appServerConfig;
|
|
249
258
|
}
|
|
250
259
|
|
|
260
|
+
determineHttpsUsage()
|
|
261
|
+
{
|
|
262
|
+
if(this.config.publicUrl && this.config.publicUrl.startsWith('https://')){
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return this.config.host.startsWith('https://');
|
|
266
|
+
}
|
|
267
|
+
|
|
251
268
|
async initializeCmsAfterInstall(props)
|
|
252
269
|
{
|
|
253
270
|
try {
|
|
271
|
+
this.config = props.mappedVariablesForConfig;
|
|
272
|
+
let appServerConfig = this.buildAppServerConfiguration();
|
|
273
|
+
Object.assign(this.appServerFactory, appServerConfig);
|
|
274
|
+
this.appServerFactory.addHttpDomainsAsDevelopment();
|
|
275
|
+
this.appServerFactory.detectDevelopmentMode();
|
|
276
|
+
this.appServerFactory.setupSecurity();
|
|
277
|
+
//Logger.debug('Development mode installation: '+this.appServerFactory.isDevelopmentMode);
|
|
254
278
|
this.rawRegisteredEntities = props.loadedEntities.rawRegisteredEntities;
|
|
255
279
|
this.entitiesTranslations = props.loadedEntities.entitiesTranslations;
|
|
256
280
|
this.entitiesConfig = props.loadedEntities.entitiesConfig;
|
|
257
281
|
this.config = props.mappedVariablesForConfig;
|
|
258
|
-
|
|
282
|
+
if(props.dataServer){
|
|
283
|
+
this.dataServer = props.dataServer;
|
|
284
|
+
this.useProvidedDataServer = true;
|
|
285
|
+
}
|
|
286
|
+
let servicesResult = await this.initializeServices();
|
|
287
|
+
if(!servicesResult){
|
|
288
|
+
Logger.critical('Failed to initialize services after installation.');
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
259
291
|
Logger.info('CMS initialized after installation on '+this.config.host+':'+this.config.port);
|
|
260
292
|
return true;
|
|
261
293
|
} catch (error) {
|
|
@@ -357,7 +389,8 @@ class Manager
|
|
|
357
389
|
this.dataServer.entityManager.entities
|
|
358
390
|
);
|
|
359
391
|
if(0 === Object.keys(this.adminEntities).length){
|
|
360
|
-
Logger.
|
|
392
|
+
Logger.critical('Admin entities generation failed - no entities available.');
|
|
393
|
+
Logger.critical('CMS cannot start without admin entities.');
|
|
361
394
|
return false;
|
|
362
395
|
}
|
|
363
396
|
return true;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - MySQLInstaller
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
9
|
+
const { Logger } = require('@reldens/utils');
|
|
10
|
+
|
|
11
|
+
class MySQLInstaller
|
|
12
|
+
{
|
|
13
|
+
|
|
14
|
+
static migrationFiles()
|
|
15
|
+
{
|
|
16
|
+
return {
|
|
17
|
+
'install-cms-tables': 'install.sql',
|
|
18
|
+
'install-user-auth': 'users-authentication.sql',
|
|
19
|
+
'install-default-user': 'default-user.sql',
|
|
20
|
+
'install-default-homepage': 'default-homepage.sql',
|
|
21
|
+
'install-default-blocks': 'default-blocks.sql',
|
|
22
|
+
'install-entity-access': 'default-entity-access.sql',
|
|
23
|
+
'install-dynamic-forms': 'default-forms.sql'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static async executeQueryFile(isMarked, fileName, dbDriver, migrationsPath)
|
|
28
|
+
{
|
|
29
|
+
if('on' !== isMarked){
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
let sqlFileContent = FileHandler.readFile(FileHandler.joinPaths(migrationsPath, fileName));
|
|
33
|
+
if(!sqlFileContent){
|
|
34
|
+
Logger.error('SQL file "'+fileName+'" not found.');
|
|
35
|
+
return '/?error=sql-file-not-found&file-name='+fileName;
|
|
36
|
+
}
|
|
37
|
+
let queryExecutionResult = await dbDriver.rawQuery(sqlFileContent);
|
|
38
|
+
if(!queryExecutionResult){
|
|
39
|
+
Logger.error('SQL file "'+fileName+'" raw execution failed.');
|
|
40
|
+
return '/?error=sql-file-execution-error&file-name='+fileName;
|
|
41
|
+
}
|
|
42
|
+
Logger.info('SQL file "'+fileName+'" raw execution successfully.');
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static async generateMinimalPrismaClient(dbConfig, projectRoot)
|
|
47
|
+
{
|
|
48
|
+
let prismaPath = FileHandler.joinPaths(projectRoot, 'prisma');
|
|
49
|
+
let schemaPath = FileHandler.joinPaths(prismaPath, 'schema.prisma');
|
|
50
|
+
FileHandler.createFolder(prismaPath);
|
|
51
|
+
let schemaContent = 'generator client {\n'
|
|
52
|
+
+ ' provider = "prisma-client-js"\n'
|
|
53
|
+
+ ' output = "./client"\n'
|
|
54
|
+
+ '}\n'
|
|
55
|
+
+ '\n'
|
|
56
|
+
+ 'datasource db {\n'
|
|
57
|
+
+ ' provider = "' + dbConfig.client + '"\n'
|
|
58
|
+
+ ' url = env("RELDENS_DB_URL")\n'
|
|
59
|
+
+ '}';
|
|
60
|
+
FileHandler.writeFile(schemaPath, schemaContent);
|
|
61
|
+
Logger.info('Running prisma generate...');
|
|
62
|
+
try {
|
|
63
|
+
execSync('npx prisma generate', { stdio: 'inherit', cwd: projectRoot });
|
|
64
|
+
let clientPath = FileHandler.joinPaths(projectRoot, 'prisma', 'client');
|
|
65
|
+
let { PrismaClient } = require(clientPath);
|
|
66
|
+
return new PrismaClient();
|
|
67
|
+
} catch(error) {
|
|
68
|
+
Logger.error('Prisma generate failed: '+error.message);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static async createPrismaClient(projectRoot)
|
|
74
|
+
{
|
|
75
|
+
try {
|
|
76
|
+
let clientPath = FileHandler.joinPaths(projectRoot, 'prisma', 'client');
|
|
77
|
+
if(!FileHandler.exists(clientPath)){
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const { PrismaClient } = require(clientPath);
|
|
81
|
+
let client = new PrismaClient();
|
|
82
|
+
await client.$connect();
|
|
83
|
+
return client;
|
|
84
|
+
} catch(error){
|
|
85
|
+
Logger.error('Prisma client creation failed: '+error.message);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports.MySQLInstaller = MySQLInstaller;
|