@reldens/cms 0.26.0 → 0.28.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/install/index.html +128 -124
- package/lib/frontend.js +274 -268
- package/lib/installer.js +500 -497
- package/lib/manager.js +515 -507
- package/lib/template-engine/asset-transformer.js +43 -41
- package/lib/template-engine/system-variables-provider.js +127 -108
- package/lib/template-engine/url-transformer.js +41 -41
- package/lib/template-engine.js +223 -213
- package/package.json +3 -3
- package/templates/.env.dist +1 -0
package/lib/installer.js
CHANGED
|
@@ -1,497 +1,500 @@
|
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
let
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
FileHandler.
|
|
424
|
-
FileHandler.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
FileHandler.createFolder(this.
|
|
441
|
-
FileHandler.createFolder(this.
|
|
442
|
-
FileHandler.createFolder(this.
|
|
443
|
-
FileHandler.createFolder(this.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
'
|
|
448
|
-
'
|
|
449
|
-
'
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
'app-
|
|
486
|
-
'
|
|
487
|
-
'
|
|
488
|
-
'
|
|
489
|
-
'db-
|
|
490
|
-
'db-
|
|
491
|
-
'db-
|
|
492
|
-
'db-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
+
publicUrl: configVariables.publicUrl,
|
|
348
|
+
adminPath: configVariables.adminPath,
|
|
349
|
+
adminSecret: configVariables.adminSecret,
|
|
350
|
+
dbClient: configVariables.database.client,
|
|
351
|
+
dbHost: configVariables.database.host,
|
|
352
|
+
dbPort: configVariables.database.port,
|
|
353
|
+
dbName: configVariables.database.name,
|
|
354
|
+
dbUser: configVariables.database.user,
|
|
355
|
+
dbPassword: configVariables.database.password,
|
|
356
|
+
dbDriver: configVariables.database.driver
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
mapVariablesForConfig(templateVariables)
|
|
361
|
+
{
|
|
362
|
+
return {
|
|
363
|
+
host: sc.get(templateVariables, 'app-host', 'http://localhost'),
|
|
364
|
+
port: Number(sc.get(templateVariables, 'app-port', 8080)),
|
|
365
|
+
publicUrl: sc.get(templateVariables, 'app-public-url', ''),
|
|
366
|
+
adminPath: sc.get(templateVariables, 'app-admin-path', '/reldens-admin'),
|
|
367
|
+
adminSecret: sc.get(templateVariables, 'app-admin-secret', Encryptor.generateSecretKey()),
|
|
368
|
+
database: {
|
|
369
|
+
client: sc.get(templateVariables, 'db-client', 'mysql'),
|
|
370
|
+
host: sc.get(templateVariables, 'db-host', 'localhost'),
|
|
371
|
+
port: Number(sc.get(templateVariables, 'db-port', 3306)),
|
|
372
|
+
name: sc.get(templateVariables, 'db-name', 'reldens_cms'),
|
|
373
|
+
user: sc.get(templateVariables, 'db-username', ''),
|
|
374
|
+
password: sc.get(templateVariables, 'db-password', ''),
|
|
375
|
+
driver: sc.get(templateVariables, 'db-storage-driver', 'prisma')
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async createIndexJsFile(templateVariables)
|
|
381
|
+
{
|
|
382
|
+
if(!FileHandler.exists(this.indexTemplatePath)){
|
|
383
|
+
Logger.error('Index.js template not found: ' + this.indexTemplatePath);
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
let indexTemplate = FileHandler.readFile(this.indexTemplatePath);
|
|
387
|
+
let driverKey = templateVariables['db-storage-driver'];
|
|
388
|
+
let templateParams = {driverKey};
|
|
389
|
+
if('prisma' === driverKey){
|
|
390
|
+
let prismaClientPath = FileHandler.joinPaths(this.projectRoot, 'prisma', 'client');
|
|
391
|
+
templateParams.prismaClientImports = 'const { PrismaClient } = require(\'' + prismaClientPath + '\');';
|
|
392
|
+
templateParams.prismaClientParam = ',\n prismaClient: new PrismaClient()';
|
|
393
|
+
}
|
|
394
|
+
if('prisma' !== driverKey){
|
|
395
|
+
templateParams.prismaClientImports = '';
|
|
396
|
+
templateParams.prismaClientParam = '';
|
|
397
|
+
}
|
|
398
|
+
let indexContent = this.renderEngine.render(indexTemplate, templateParams);
|
|
399
|
+
let indexFilePath = FileHandler.joinPaths(this.projectRoot, 'index.js');
|
|
400
|
+
if(FileHandler.exists(indexFilePath)){
|
|
401
|
+
Logger.info('Index.js file already exists, the CMS installer will not override the existent one.');
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
return FileHandler.writeFile(indexFilePath, indexContent);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async createLockFile()
|
|
408
|
+
{
|
|
409
|
+
return FileHandler.writeFile(this.installLockPath, 'Installation completed on '+new Date().toISOString());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async copyAdminDirectory()
|
|
413
|
+
{
|
|
414
|
+
let projectAdminPath = FileHandler.joinPaths(this.projectRoot, 'admin');
|
|
415
|
+
if(FileHandler.exists(projectAdminPath)){
|
|
416
|
+
Logger.info('Admin folder already exists in project root.');
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
if(!FileHandler.exists(this.moduleAdminPath)){
|
|
420
|
+
Logger.error('Admin folder not found in module path: '+this.moduleAdminPath);
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
let projectAdminTemplates = FileHandler.joinPaths(projectAdminPath, 'templates');
|
|
424
|
+
FileHandler.copyFolderSync(this.moduleAdminTemplatesPath, projectAdminTemplates);
|
|
425
|
+
FileHandler.copyFolderSync(this.moduleAdminAssetsPath, this.projectPublicAssetsPath);
|
|
426
|
+
FileHandler.copyFile(
|
|
427
|
+
FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.css'),
|
|
428
|
+
FileHandler.joinPaths(this.projectCssPath, 'reldens-admin-client.css'),
|
|
429
|
+
);
|
|
430
|
+
FileHandler.copyFile(
|
|
431
|
+
FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.js'),
|
|
432
|
+
FileHandler.joinPaths(this.projectJsPath, 'reldens-admin-client.js'),
|
|
433
|
+
);
|
|
434
|
+
Logger.info('Admin folder copied to project root.');
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async prepareProjectDirectories()
|
|
439
|
+
{
|
|
440
|
+
FileHandler.createFolder(this.projectTemplatesPath);
|
|
441
|
+
FileHandler.createFolder(FileHandler.joinPaths(this.projectTemplatesPath, 'layouts'));
|
|
442
|
+
FileHandler.createFolder(this.projectPublicPath);
|
|
443
|
+
FileHandler.createFolder(this.projectPublicAssetsPath);
|
|
444
|
+
FileHandler.createFolder(this.projectCssPath);
|
|
445
|
+
FileHandler.createFolder(this.projectJsPath);
|
|
446
|
+
let baseFiles = [
|
|
447
|
+
'page.html',
|
|
448
|
+
'404.html',
|
|
449
|
+
'browserconfig.xml',
|
|
450
|
+
'favicon.ico',
|
|
451
|
+
'site.webmanifest'
|
|
452
|
+
];
|
|
453
|
+
for(let fileName of baseFiles){
|
|
454
|
+
FileHandler.copyFile(
|
|
455
|
+
FileHandler.joinPaths(this.defaultTemplatesPath, fileName),
|
|
456
|
+
FileHandler.joinPaths(this.projectTemplatesPath, fileName)
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
FileHandler.copyFile(
|
|
460
|
+
FileHandler.joinPaths(this.defaultTemplatesPath, 'layouts', 'default.html'),
|
|
461
|
+
FileHandler.joinPaths(this.projectTemplatesPath, 'layouts', 'default.html')
|
|
462
|
+
);
|
|
463
|
+
FileHandler.copyFile(
|
|
464
|
+
FileHandler.joinPaths(this.defaultTemplatesPath, 'css', 'styles.css'),
|
|
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
|
+
);
|
|
471
|
+
FileHandler.copyFolderSync(
|
|
472
|
+
FileHandler.joinPaths(this.defaultTemplatesPath, 'partials'),
|
|
473
|
+
FileHandler.joinPaths(this.projectTemplatesPath, 'partials')
|
|
474
|
+
);
|
|
475
|
+
FileHandler.copyFolderSync(
|
|
476
|
+
FileHandler.joinPaths(this.defaultTemplatesPath, 'domains'),
|
|
477
|
+
FileHandler.joinPaths(this.projectTemplatesPath, 'domains')
|
|
478
|
+
);
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
fetchDefaults()
|
|
483
|
+
{
|
|
484
|
+
return {
|
|
485
|
+
'app-host': process.env.RELDENS_APP_HOST || 'http://localhost',
|
|
486
|
+
'app-port': process.env.RELDENS_APP_PORT || '8080',
|
|
487
|
+
'app-public-url': process.env.RELDENS_PUBLIC_URL || '',
|
|
488
|
+
'app-admin-path': process.env.RELDENS_ADMIN_ROUTE_PATH || '/reldens-admin',
|
|
489
|
+
'db-storage-driver': process.env.RELDENS_STORAGE_DRIVER || 'prisma',
|
|
490
|
+
'db-client': process.env.RELDENS_DB_CLIENT || 'mysql',
|
|
491
|
+
'db-host': process.env.RELDENS_DB_HOST || 'localhost',
|
|
492
|
+
'db-port': process.env.RELDENS_DB_PORT || '3306',
|
|
493
|
+
'db-name': process.env.RELDENS_DB_NAME || 'reldens_cms',
|
|
494
|
+
'db-username': process.env.RELDENS_DB_USER || '',
|
|
495
|
+
'db-password': process.env.RELDENS_DB_PASSWORD || ''
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
module.exports.Installer = Installer;
|