@reldens/cms 0.85.0 → 0.86.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/manager.js CHANGED
@@ -1,306 +1,310 @@
1
- /**
2
- *
3
- * Reldens - CMS - Manager
4
- *
5
- */
6
-
7
- const { TemplatesList } = require('./templates-list');
8
- const { AdminTemplatesLoader } = require('./admin-templates-loader');
9
- const { MimeTypes } = require('./mime-types');
10
- const { AllowedExtensions } = require('./allowed-extensions');
11
- const { TemplatesToPathMapper } = require('./templates-to-path-mapper');
12
- const { AdminEntitiesGenerator } = require('./admin-entities-generator');
13
- const { CmsPagesRouteManager } = require('./cms-pages-route-manager');
14
- const { Installer } = require('./installer');
15
- const { CacheManager } = require('./cache/cache-manager');
16
- const { TemplateReloader } = require('./template-reloader');
17
- const { ManagerComponentValidator } = require('./manager-component-validator');
18
- const { ManagerConfigLoader } = require('./manager-config-loader');
19
- const { ManagerServicesInitializer } = require('./manager-services-initializer');
20
- const { EventsManagerSingleton, Logger, sc } = require('@reldens/utils');
21
- const { AppServerFactory, FileHandler } = require('@reldens/server-utils');
22
- const dotenv = require('dotenv');
23
- const mustache = require('mustache');
24
-
25
- class Manager
26
- {
27
-
28
- constructor(props = {})
29
- {
30
- this.projectRoot = sc.get(props, 'projectRoot', './');
31
- this.envFilePath = FileHandler.joinPaths(this.projectRoot, '.env');
32
- this.installLockPath = FileHandler.joinPaths(this.projectRoot, 'install.lock');
33
- dotenv.config({path: this.envFilePath});
34
- this.config = ManagerConfigLoader.loadFromEnv();
35
- this.adminTranslations = sc.get(props, 'adminTranslations', {});
36
- this.adminEntities = sc.get(props, 'adminEntities', {});
37
- this.rawRegisteredEntities = sc.get(props, 'rawRegisteredEntities', {});
38
- this.entitiesTranslations = sc.get(props, 'entitiesTranslations', {});
39
- this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
40
- this.entitiesConfigOverride = sc.get(props, 'entitiesConfigOverride', {});
41
- this.processedEntities = sc.get(props, 'processedEntities', {});
42
- this.entityAccess = sc.get(props, 'entityAccess', {});
43
- this.authenticationMethod = sc.get(props, 'authenticationMethod', 'db-users');
44
- this.authenticationCallback = sc.get(props, 'authenticationCallback', false);
45
- this.enablePasswordEncryption = sc.get(props, 'enablePasswordEncryption', true);
46
- this.events = sc.get(props, 'events', EventsManagerSingleton);
47
- this.adminTemplatesList = sc.get(props, 'adminTemplatesList', TemplatesList);
48
- this.projectAdminPath = FileHandler.joinPaths(this.projectRoot, 'admin');
49
- this.projectAdminTemplatesPath = FileHandler.joinPaths(this.projectAdminPath, 'templates');
50
- this.mimeTypes = sc.get(props, 'mimeTypes', MimeTypes);
51
- this.allowedExtensions = sc.get(props, 'allowedExtensions', AllowedExtensions);
52
- this.adminRoleId = sc.get(props, 'adminRoleId', 99);
53
- this.mappedAdminTemplates = TemplatesToPathMapper.map(this.adminTemplatesList, this.projectAdminTemplatesPath);
54
- this.stylesFilePath = sc.get(props, 'stylesFilePath', '/css/reldens-admin-client.css');
55
- this.scriptsFilePath = sc.get(props, 'scriptsFilePath', '/js/reldens-admin-client.js');
56
- this.companyName = sc.get(props, 'companyName', 'Reldens - CMS');
57
- this.logo = sc.get(props, 'logo', '/assets/web/reldens-your-logo-mage.png');
58
- this.favicon = sc.get(props, 'favicon', '/assets/web/favicon.ico');
59
- this.defaultDomain = sc.get(props, 'defaultDomain', (process.env.RELDENS_DEFAULT_DOMAIN || ''));
60
- this.domainMapping = sc.get(props, 'domainMapping', sc.toJson(process.env.RELDENS_DOMAIN_MAPPING));
61
- this.siteKeyMapping = sc.get(props, 'siteKeyMapping', sc.toJson(process.env.RELDENS_SITE_KEY_MAPPING));
62
- this.domainPublicUrlMapping = sc.get(
63
- props,
64
- 'domainPublicUrlMapping',
65
- sc.toJson(process.env.RELDENS_DOMAIN_PUBLIC_URL_MAPPING, {})
66
- );
67
- this.domainCdnMapping = sc.get(
68
- props,
69
- 'domainCdnMapping',
70
- sc.toJson(process.env.RELDENS_DOMAIN_CDN_MAPPING, {})
71
- );
72
- this.templateExtensions = sc.get(
73
- props,
74
- 'templateExtensions',
75
- ['.html', '.mustache', '.template', '.txt', '.xml', '.json']
76
- );
77
- this.cache = sc.get(props, 'cache', false);
78
- this.reloadTime = sc.get(props, 'reloadTime', 0);
79
- this.app = sc.get(props, 'app', false);
80
- this.appServer = sc.get(props, 'appServer', false);
81
- this.dataServer = sc.get(props, 'dataServer', false);
82
- this.adminManager = sc.get(props, 'adminManager', false);
83
- this.frontend = sc.get(props, 'frontend', false);
84
- this.renderEngine = sc.get(props, 'renderEngine', mustache);
85
- this.prismaClient = sc.get(props, 'prismaClient', false);
86
- this.domains = sc.get(props, 'domains', []);
87
- this.developmentPatterns = sc.get(props, 'developmentPatterns', []);
88
- this.developmentEnvironments = sc.get(props, 'developmentEnvironments', []);
89
- this.developmentPorts = sc.get(props, 'developmentPorts', []);
90
- this.developmentMultiplier = sc.get(props, 'developmentMultiplier', 10);
91
- this.appServerConfig = sc.get(props, 'appServerConfig', {});
92
- this.useDefaultErrorCallback = sc.get(props, 'useDefaultErrorCallback', true);
93
- this.developmentExternalDomains = sc.get(props, 'developmentExternalDomains', {});
94
- this.appServerFactory = new AppServerFactory();
95
- this.adminEntitiesGenerator = new AdminEntitiesGenerator();
96
- this.cacheManager = new CacheManager({
97
- projectRoot: this.projectRoot,
98
- enabled: this.cache,
99
- domainMapping: this.domainMapping
100
- });
101
- this.templateReloader = new TemplateReloader({
102
- reloadTime: this.reloadTime,
103
- events: this.events,
104
- adminTemplatesLoader: AdminTemplatesLoader,
105
- mappedAdminTemplates: this.mappedAdminTemplates,
106
- templatesPath: FileHandler.joinPaths(this.projectRoot, 'templates'),
107
- templateExtensions: this.templateExtensions
108
- });
109
- this.installer = new Installer({
110
- projectRoot: this.projectRoot,
111
- prismaClient: this.prismaClient,
112
- postInstallCallback: this.initializeCmsAfterInstall.bind(this)
113
- });
114
- this.servicesInitializer = new ManagerServicesInitializer(this);
115
- this.useProvidedServer = ManagerComponentValidator.validateProvidedServer(this.app, this.appServer);
116
- this.useProvidedDataServer = ManagerComponentValidator.validateProvidedDataServer(this.dataServer);
117
- this.useProvidedAdminManager = ManagerComponentValidator.validateProvidedAdminManager(this.adminManager);
118
- this.useProvidedFrontend = ManagerComponentValidator.validateProvidedFrontend(this.frontend);
119
- this.cmsPagesRouteManager = new CmsPagesRouteManager({
120
- dataServer: this.dataServer,
121
- events: this.events
122
- });
123
- }
124
-
125
- isInstalled()
126
- {
127
- return FileHandler.exists(this.installLockPath);
128
- }
129
-
130
- async start()
131
- {
132
- if(!this.useProvidedServer){
133
- let appServerConfig = this.buildAppServerConfiguration();
134
- let createdAppServer = this.appServerFactory.createAppServer(appServerConfig);
135
- if(this.appServerFactory.error.message){
136
- Logger.error('App server error: '+this.appServerFactory.error.message);
137
- return false;
138
- }
139
- this.app = createdAppServer.app;
140
- this.appServer = createdAppServer.appServer;
141
- }
142
- if(!this.isInstalled()){
143
- Logger.info('CMS not installed, preparing setup');
144
- await this.installer.configureAppServerRoutes(
145
- this.app,
146
- this.appServer,
147
- this.appServerFactory,
148
- this.renderEngine
149
- );
150
- if(!this.useProvidedServer){
151
- await this.appServer.listen(this.config.port);
152
- }
153
- Logger.info('Installer running on '+this.config.host+':'+this.config.port);
154
- return true;
155
- }
156
- try {
157
- await this.servicesInitializer.initializeServices();
158
- Logger.info('CMS running on '+this.config.host+':'+this.config.port);
159
- return true;
160
- } catch (error) {
161
- Logger.critical('Failed to start CMS: '+error.message);
162
- return false;
163
- }
164
- }
165
-
166
- buildAppServerConfiguration()
167
- {
168
- let useHelmet = this.isInstalled();
169
- let useHttps = this.config.host.startsWith('https://');
170
- let baseConfig = {
171
- port: this.config.port,
172
- useHttps,
173
- useHelmet,
174
- domainMapping: this.domainMapping || {},
175
- defaultDomain: this.defaultDomain,
176
- developmentPatterns: this.developmentPatterns,
177
- developmentEnvironments: this.developmentEnvironments,
178
- developmentPorts: this.developmentPorts,
179
- developmentMultiplier: this.developmentMultiplier,
180
- developmentExternalDomains: this.developmentExternalDomains
181
- };
182
- if(this.useDefaultErrorCallback && !this.appServerConfig.onError){
183
- baseConfig.onError = (event) => {
184
- if(!event.error){
185
- return;
186
- }
187
- let errorKey = event.key || 'unknown';
188
- let errorMessage = 'string' === typeof event.error ? event.error : event.error.message;
189
- let errorCode = event.error.code || '';
190
- let errorStack = event.error.stack || '';
191
- let logParts = ['Server error - key: '+errorKey];
192
- if(event.hostname){
193
- logParts.push('hostname: '+event.hostname);
194
- }
195
- if(event.path){
196
- logParts.push('path: '+event.path);
197
- }
198
- if(errorCode){
199
- logParts.push('code: '+errorCode);
200
- }
201
- if(errorMessage){
202
- logParts.push('message: '+errorMessage);
203
- }
204
- if(errorStack){
205
- logParts.push('stack: '+errorStack);
206
- }
207
- Logger.error(logParts.join(', '));
208
- };
209
- }
210
- let appServerConfig = Object.assign({}, baseConfig, this.appServerConfig);
211
- if(this.domainMapping && 'object' === typeof this.domainMapping){
212
- this.appServerFactory.setDomainMapping(this.domainMapping);
213
- this.validateCdnMappingsInDevelopment();
214
- if(!useHttps){
215
- let mappingKeys = Object.keys(this.domainMapping);
216
- for(let domain of mappingKeys){
217
- this.appServerFactory.addDevelopmentDomain(domain);
218
- }
219
- }
220
- }
221
- if(sc.isArray(this.domains) && 0 < this.domains.length){
222
- for(let domain of this.domains){
223
- this.appServerFactory.addDomain(domain);
224
- }
225
- }
226
- return appServerConfig;
227
- }
228
-
229
- isCdnUrlInDirectives(cdnUrlWithProtocol, cdnHostname)
230
- {
231
- let directiveKeys = Object.keys(this.developmentExternalDomains);
232
- for(let directiveKey of directiveKeys){
233
- let domains = this.developmentExternalDomains[directiveKey];
234
- if(!sc.isArray(domains)){
235
- continue;
236
- }
237
- if(domains.includes(cdnUrlWithProtocol) || domains.includes(cdnHostname)){
238
- return true;
239
- }
240
- }
241
- return false;
242
- }
243
-
244
- validateCdnMappingsInDevelopment()
245
- {
246
- if(!sc.isObject(this.domainCdnMapping) || sc.isArray(this.domainCdnMapping)){
247
- return;
248
- }
249
- if(0 === Object.keys(this.domainCdnMapping).length){
250
- return;
251
- }
252
- if(!sc.isObject(this.developmentExternalDomains) || sc.isArray(this.developmentExternalDomains)){
253
- Logger.info('CDN mappings configured but developmentExternalDomains not provided. '
254
- +'CDN assets may fail in development mode due to CORS. '
255
- +'Add CDN domains to developmentExternalDomains configuration.');
256
- return;
257
- }
258
- let domainKeys = Object.keys(this.domainCdnMapping);
259
- let domainsWithMissingCdn = [];
260
- for(let domain of domainKeys){
261
- let cdnUrl = this.domainCdnMapping[domain];
262
- let cdnHostname = cdnUrl.replace(/^https?:\/\//, '').split('/')[0];
263
- let cdnUrlWithProtocol = cdnUrl.split('/')[0];
264
- if(!this.isCdnUrlInDirectives(cdnUrlWithProtocol, cdnHostname)){
265
- domainsWithMissingCdn.push(domain);
266
- }
267
- }
268
- if(0 < domainsWithMissingCdn.length){
269
- Logger.info('CDN mapping for domains: '+domainsWithMissingCdn.join(', ')
270
- +' not found in CSP directives (scriptSrc, styleSrc, fontSrc, imgSrc, connectSrc, manifestSrc) '
271
- +'within developmentExternalDomains. Add CDN URLs to avoid CORS issues in development mode.');
272
- }
273
- }
274
-
275
- async initializeCmsAfterInstall(props)
276
- {
277
- try {
278
- this.config = props.mappedVariablesForConfig;
279
- let appServerConfig = this.buildAppServerConfiguration();
280
- Object.assign(this.appServerFactory, appServerConfig);
281
- this.appServerFactory.addHttpDomainsAsDevelopment();
282
- this.appServerFactory.detectDevelopmentMode();
283
- this.appServerFactory.setupSecurity();
284
- this.rawRegisteredEntities = props.loadedEntities.rawRegisteredEntities;
285
- this.entitiesTranslations = props.loadedEntities.entitiesTranslations;
286
- this.entitiesConfig = props.loadedEntities.entitiesConfig;
287
- this.config = props.mappedVariablesForConfig;
288
- if(props.dataServer){
289
- this.dataServer = props.dataServer;
290
- this.useProvidedDataServer = true;
291
- }
292
- let servicesResult = await this.servicesInitializer.initializeServices();
293
- if(!servicesResult){
294
- Logger.critical('Failed to initialize services after installation.');
295
- return false;
296
- }
297
- Logger.info('CMS initialized after installation on '+this.config.host+':'+this.config.port);
298
- return true;
299
- } catch (error) {
300
- Logger.critical('Failed to initialize CMS after installation: '+error.message);
301
- return false;
302
- }
303
- }
304
- }
305
-
306
- module.exports.Manager = Manager;
1
+ /**
2
+ *
3
+ * Reldens - CMS - Manager
4
+ *
5
+ */
6
+
7
+ const { TemplatesList } = require('./templates-list');
8
+ const { AdminTemplatesLoader } = require('./admin-templates-loader');
9
+ const { MimeTypes } = require('./mime-types');
10
+ const { AllowedExtensions } = require('./allowed-extensions');
11
+ const { TemplatesToPathMapper } = require('./templates-to-path-mapper');
12
+ const { AdminEntitiesGenerator } = require('./admin-entities-generator');
13
+ const { CmsPagesRouteManager } = require('./cms-pages-route-manager');
14
+ const { Installer } = require('./installer');
15
+ const { CacheManager } = require('./cache/cache-manager');
16
+ const { TemplateReloader } = require('./template-reloader');
17
+ const { ManagerComponentValidator } = require('./manager-component-validator');
18
+ const { ManagerConfigLoader } = require('./manager-config-loader');
19
+ const { ManagerServicesInitializer } = require('./manager-services-initializer');
20
+ const { EventsManagerSingleton, Logger, sc } = require('@reldens/utils');
21
+ const { AppServerFactory, FileHandler } = require('@reldens/server-utils');
22
+ const dotenv = require('dotenv');
23
+ const mustache = require('mustache');
24
+
25
+ class Manager
26
+ {
27
+
28
+ constructor(props = {})
29
+ {
30
+ this.projectRoot = sc.get(props, 'projectRoot', './');
31
+ this.envFilePath = FileHandler.joinPaths(this.projectRoot, '.env');
32
+ this.installLockPath = FileHandler.joinPaths(this.projectRoot, 'install.lock');
33
+ dotenv.config({path: this.envFilePath});
34
+ this.config = ManagerConfigLoader.loadFromEnv();
35
+ this.adminTranslations = sc.get(props, 'adminTranslations', {});
36
+ this.adminEntities = sc.get(props, 'adminEntities', {});
37
+ this.rawRegisteredEntities = sc.get(props, 'rawRegisteredEntities', {});
38
+ this.entitiesTranslations = sc.get(props, 'entitiesTranslations', {});
39
+ this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
40
+ this.entitiesConfigOverride = sc.get(props, 'entitiesConfigOverride', {});
41
+ this.processedEntities = sc.get(props, 'processedEntities', {});
42
+ this.entityAccess = sc.get(props, 'entityAccess', {});
43
+ this.authenticationMethod = sc.get(props, 'authenticationMethod', 'db-users');
44
+ this.authenticationCallback = sc.get(props, 'authenticationCallback', false);
45
+ this.enablePasswordEncryption = sc.get(props, 'enablePasswordEncryption', true);
46
+ this.events = sc.get(props, 'events', EventsManagerSingleton);
47
+ this.adminTemplatesList = sc.get(props, 'adminTemplatesList', TemplatesList);
48
+ this.projectAdminPath = FileHandler.joinPaths(this.projectRoot, 'admin');
49
+ this.projectAdminTemplatesPath = FileHandler.joinPaths(this.projectAdminPath, 'templates');
50
+ this.mimeTypes = sc.get(props, 'mimeTypes', MimeTypes);
51
+ this.allowedExtensions = sc.get(props, 'allowedExtensions', AllowedExtensions);
52
+ this.adminRoleId = sc.get(props, 'adminRoleId', 99);
53
+ this.mappedAdminTemplates = TemplatesToPathMapper.map(this.adminTemplatesList, this.projectAdminTemplatesPath);
54
+ this.stylesFilePath = sc.get(props, 'stylesFilePath', '/css/reldens-admin-client.css');
55
+ this.scriptsFilePath = sc.get(props, 'scriptsFilePath', '/js/reldens-admin-client.js');
56
+ this.companyName = sc.get(props, 'companyName', 'Reldens - CMS');
57
+ this.logo = sc.get(props, 'logo', '/assets/web/reldens-your-logo-mage.png');
58
+ this.favicon = sc.get(props, 'favicon', '/assets/web/favicon.ico');
59
+ this.defaultDomain = sc.get(props, 'defaultDomain', (process.env.RELDENS_DEFAULT_DOMAIN || ''));
60
+ this.domainMapping = sc.get(props, 'domainMapping', sc.toJson(process.env.RELDENS_DOMAIN_MAPPING));
61
+ this.siteKeyMapping = sc.get(props, 'siteKeyMapping', sc.toJson(process.env.RELDENS_SITE_KEY_MAPPING));
62
+ this.domainPublicUrlMapping = sc.get(
63
+ props,
64
+ 'domainPublicUrlMapping',
65
+ sc.toJson(process.env.RELDENS_DOMAIN_PUBLIC_URL_MAPPING, {})
66
+ );
67
+ this.domainCdnMapping = sc.get(
68
+ props,
69
+ 'domainCdnMapping',
70
+ sc.toJson(process.env.RELDENS_DOMAIN_CDN_MAPPING, {})
71
+ );
72
+ this.templateExtensions = sc.get(
73
+ props,
74
+ 'templateExtensions',
75
+ ['.html', '.mustache', '.template', '.txt', '.xml', '.json']
76
+ );
77
+ this.cache = sc.get(props, 'cache', false);
78
+ this.reloadTime = sc.get(props, 'reloadTime', 0);
79
+ this.app = sc.get(props, 'app', false);
80
+ this.appServer = sc.get(props, 'appServer', false);
81
+ this.dataServer = sc.get(props, 'dataServer', false);
82
+ this.adminManager = sc.get(props, 'adminManager', false);
83
+ this.frontend = sc.get(props, 'frontend', false);
84
+ this.renderEngine = sc.get(props, 'renderEngine', mustache);
85
+ this.prismaModules = sc.get(props, 'prismaModules', false);
86
+ this.prismaAdapter = sc.get(props, 'prismaAdapter', this.config.database.prismaAdapter);
87
+ this.prismaAdapterClass = sc.get(props, 'prismaAdapterClass', this.config.database.prismaAdapterClass);
88
+ this.domains = sc.get(props, 'domains', []);
89
+ this.developmentPatterns = sc.get(props, 'developmentPatterns', []);
90
+ this.developmentEnvironments = sc.get(props, 'developmentEnvironments', []);
91
+ this.developmentPorts = sc.get(props, 'developmentPorts', []);
92
+ this.developmentMultiplier = sc.get(props, 'developmentMultiplier', 10);
93
+ this.appServerConfig = sc.get(props, 'appServerConfig', {});
94
+ this.useDefaultErrorCallback = sc.get(props, 'useDefaultErrorCallback', true);
95
+ this.developmentExternalDomains = sc.get(props, 'developmentExternalDomains', {});
96
+ this.appServerFactory = new AppServerFactory();
97
+ this.adminEntitiesGenerator = new AdminEntitiesGenerator();
98
+ this.cacheManager = new CacheManager({
99
+ projectRoot: this.projectRoot,
100
+ enabled: this.cache,
101
+ domainMapping: this.domainMapping
102
+ });
103
+ this.templateReloader = new TemplateReloader({
104
+ reloadTime: this.reloadTime,
105
+ events: this.events,
106
+ adminTemplatesLoader: AdminTemplatesLoader,
107
+ mappedAdminTemplates: this.mappedAdminTemplates,
108
+ templatesPath: FileHandler.joinPaths(this.projectRoot, 'templates'),
109
+ templateExtensions: this.templateExtensions
110
+ });
111
+ this.installer = new Installer({
112
+ projectRoot: this.projectRoot,
113
+ prismaModules: this.prismaModules,
114
+ prismaAdapter: this.prismaAdapter,
115
+ prismaAdapterClass: this.prismaAdapterClass,
116
+ postInstallCallback: this.initializeCmsAfterInstall.bind(this)
117
+ });
118
+ this.servicesInitializer = new ManagerServicesInitializer(this);
119
+ this.useProvidedServer = ManagerComponentValidator.validateProvidedServer(this.app, this.appServer);
120
+ this.useProvidedDataServer = ManagerComponentValidator.validateProvidedDataServer(this.dataServer);
121
+ this.useProvidedAdminManager = ManagerComponentValidator.validateProvidedAdminManager(this.adminManager);
122
+ this.useProvidedFrontend = ManagerComponentValidator.validateProvidedFrontend(this.frontend);
123
+ this.cmsPagesRouteManager = new CmsPagesRouteManager({
124
+ dataServer: this.dataServer,
125
+ events: this.events
126
+ });
127
+ }
128
+
129
+ isInstalled()
130
+ {
131
+ return FileHandler.exists(this.installLockPath);
132
+ }
133
+
134
+ async start()
135
+ {
136
+ if(!this.useProvidedServer){
137
+ let appServerConfig = this.buildAppServerConfiguration();
138
+ let createdAppServer = this.appServerFactory.createAppServer(appServerConfig);
139
+ if(this.appServerFactory.error.message){
140
+ Logger.error('App server error: '+this.appServerFactory.error.message);
141
+ return false;
142
+ }
143
+ this.app = createdAppServer.app;
144
+ this.appServer = createdAppServer.appServer;
145
+ }
146
+ if(!this.isInstalled()){
147
+ Logger.info('CMS not installed, preparing setup');
148
+ await this.installer.configureAppServerRoutes(
149
+ this.app,
150
+ this.appServer,
151
+ this.appServerFactory,
152
+ this.renderEngine
153
+ );
154
+ if(!this.useProvidedServer){
155
+ await this.appServer.listen(this.config.port);
156
+ }
157
+ Logger.info('Installer running on '+this.config.host+':'+this.config.port);
158
+ return true;
159
+ }
160
+ try {
161
+ await this.servicesInitializer.initializeServices();
162
+ Logger.info('CMS running on '+this.config.host+':'+this.config.port);
163
+ return true;
164
+ } catch (error) {
165
+ Logger.critical('Failed to start CMS: '+error.message);
166
+ return false;
167
+ }
168
+ }
169
+
170
+ buildAppServerConfiguration()
171
+ {
172
+ let useHelmet = this.isInstalled();
173
+ let useHttps = this.config.host.startsWith('https://');
174
+ let baseConfig = {
175
+ port: this.config.port,
176
+ useHttps,
177
+ useHelmet,
178
+ domainMapping: this.domainMapping || {},
179
+ defaultDomain: this.defaultDomain,
180
+ developmentPatterns: this.developmentPatterns,
181
+ developmentEnvironments: this.developmentEnvironments,
182
+ developmentPorts: this.developmentPorts,
183
+ developmentMultiplier: this.developmentMultiplier,
184
+ developmentExternalDomains: this.developmentExternalDomains
185
+ };
186
+ if(this.useDefaultErrorCallback && !this.appServerConfig.onError){
187
+ baseConfig.onError = (event) => {
188
+ if(!event.error){
189
+ return;
190
+ }
191
+ let errorKey = event.key || 'unknown';
192
+ let errorMessage = 'string' === typeof event.error ? event.error : event.error.message;
193
+ let errorCode = event.error.code || '';
194
+ let errorStack = event.error.stack || '';
195
+ let logParts = ['Server error - key: '+errorKey];
196
+ if(event.hostname){
197
+ logParts.push('hostname: '+event.hostname);
198
+ }
199
+ if(event.path){
200
+ logParts.push('path: '+event.path);
201
+ }
202
+ if(errorCode){
203
+ logParts.push('code: '+errorCode);
204
+ }
205
+ if(errorMessage){
206
+ logParts.push('message: '+errorMessage);
207
+ }
208
+ if(errorStack){
209
+ logParts.push('stack: '+errorStack);
210
+ }
211
+ Logger.error(logParts.join(', '));
212
+ };
213
+ }
214
+ let appServerConfig = Object.assign({}, baseConfig, this.appServerConfig);
215
+ if(this.domainMapping && 'object' === typeof this.domainMapping){
216
+ this.appServerFactory.setDomainMapping(this.domainMapping);
217
+ this.validateCdnMappingsInDevelopment();
218
+ if(!useHttps){
219
+ let mappingKeys = Object.keys(this.domainMapping);
220
+ for(let domain of mappingKeys){
221
+ this.appServerFactory.addDevelopmentDomain(domain);
222
+ }
223
+ }
224
+ }
225
+ if(sc.isArray(this.domains) && 0 < this.domains.length){
226
+ for(let domain of this.domains){
227
+ this.appServerFactory.addDomain(domain);
228
+ }
229
+ }
230
+ return appServerConfig;
231
+ }
232
+
233
+ isCdnUrlInDirectives(cdnUrlWithProtocol, cdnHostname)
234
+ {
235
+ let directiveKeys = Object.keys(this.developmentExternalDomains);
236
+ for(let directiveKey of directiveKeys){
237
+ let domains = this.developmentExternalDomains[directiveKey];
238
+ if(!sc.isArray(domains)){
239
+ continue;
240
+ }
241
+ if(domains.includes(cdnUrlWithProtocol) || domains.includes(cdnHostname)){
242
+ return true;
243
+ }
244
+ }
245
+ return false;
246
+ }
247
+
248
+ validateCdnMappingsInDevelopment()
249
+ {
250
+ if(!sc.isObject(this.domainCdnMapping) || sc.isArray(this.domainCdnMapping)){
251
+ return;
252
+ }
253
+ if(0 === Object.keys(this.domainCdnMapping).length){
254
+ return;
255
+ }
256
+ if(!sc.isObject(this.developmentExternalDomains) || sc.isArray(this.developmentExternalDomains)){
257
+ Logger.info('CDN mappings configured but developmentExternalDomains not provided. '
258
+ +'CDN assets may fail in development mode due to CORS. '
259
+ +'Add CDN domains to developmentExternalDomains configuration.');
260
+ return;
261
+ }
262
+ let domainKeys = Object.keys(this.domainCdnMapping);
263
+ let domainsWithMissingCdn = [];
264
+ for(let domain of domainKeys){
265
+ let cdnUrl = this.domainCdnMapping[domain];
266
+ let cdnHostname = cdnUrl.replace(/^https?:\/\//, '').split('/')[0];
267
+ let cdnUrlWithProtocol = cdnUrl.split('/')[0];
268
+ if(!this.isCdnUrlInDirectives(cdnUrlWithProtocol, cdnHostname)){
269
+ domainsWithMissingCdn.push(domain);
270
+ }
271
+ }
272
+ if(0 < domainsWithMissingCdn.length){
273
+ Logger.info('CDN mapping for domains: '+domainsWithMissingCdn.join(', ')
274
+ +' not found in CSP directives (scriptSrc, styleSrc, fontSrc, imgSrc, connectSrc, manifestSrc) '
275
+ +'within developmentExternalDomains. Add CDN URLs to avoid CORS issues in development mode.');
276
+ }
277
+ }
278
+
279
+ async initializeCmsAfterInstall(props)
280
+ {
281
+ try {
282
+ this.config = props.mappedVariablesForConfig;
283
+ let appServerConfig = this.buildAppServerConfiguration();
284
+ Object.assign(this.appServerFactory, appServerConfig);
285
+ this.appServerFactory.addHttpDomainsAsDevelopment();
286
+ this.appServerFactory.detectDevelopmentMode();
287
+ this.appServerFactory.setupSecurity();
288
+ this.rawRegisteredEntities = props.loadedEntities.rawRegisteredEntities;
289
+ this.entitiesTranslations = props.loadedEntities.entitiesTranslations;
290
+ this.entitiesConfig = props.loadedEntities.entitiesConfig;
291
+ this.config = props.mappedVariablesForConfig;
292
+ if(props.dataServer){
293
+ this.dataServer = props.dataServer;
294
+ this.useProvidedDataServer = true;
295
+ }
296
+ let servicesResult = await this.servicesInitializer.initializeServices();
297
+ if(!servicesResult){
298
+ Logger.critical('Failed to initialize services after installation.');
299
+ return false;
300
+ }
301
+ Logger.info('CMS initialized after installation on '+this.config.host+':'+this.config.port);
302
+ return true;
303
+ } catch (error) {
304
+ Logger.critical('Failed to initialize CMS after installation: '+error.message);
305
+ return false;
306
+ }
307
+ }
308
+ }
309
+
310
+ module.exports.Manager = Manager;