@reldens/server-utils 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # Reldens - Server Utils
2
2
 
3
- A Node.js server toolkit providing secure application server creation, file handling, encryption, and file upload capabilities for production-ready applications.
3
+ A Node.js server toolkit providing secure application server creation, file handling, encryption, and file upload capabilities for production-ready applications with modular security configurations.
4
4
 
5
5
  [![Reldens - GitHub - Release](https://www.dwdeveloper.com/media/reldens/reldens-mmorpg-platform.png)](https://github.com/damian-pastorini/reldens)
6
6
 
7
7
  ## Features
8
8
 
9
9
  ### AppServerFactory
10
- - Complete Express.js server configuration with security defaults
10
+ - Complete Express.js server configuration with modular security
11
11
  - HTTPS/HTTP server creation with SSL certificate management
12
12
  - SNI (Server Name Indication) support for multi-domain hosting
13
13
  - Virtual host management with domain mapping
@@ -20,8 +20,18 @@ A Node.js server toolkit providing secure application server creation, file hand
20
20
  - Trusted proxy configuration
21
21
  - Request parsing with size limits and validation
22
22
  - Static file serving with security headers
23
+ - Compression middleware with smart filtering
23
24
  - Input validation utilities
24
25
 
26
+ #### Modular Security Components
27
+ The AppServerFactory now uses specialized security configurers:
28
+
29
+ - **CorsConfigurer** - Dynamic CORS origin validation with development domain support
30
+ - **DevelopmentModeDetector** - Automatic development environment detection
31
+ - **ProtocolEnforcer** - Protocol redirection with development mode awareness
32
+ - **RateLimitConfigurer** - Global and endpoint-specific rate limiting
33
+ - **SecurityConfigurer** - Helmet integration with CSP management and XSS protection
34
+
25
35
  ### FileHandler
26
36
  - Secure file system operations with path validation
27
37
  - File and folder creation, copying, and removal
@@ -34,6 +44,8 @@ A Node.js server toolkit providing secure application server creation, file hand
34
44
  - Temporary file creation
35
45
  - File quarantine functionality for security threats
36
46
  - Binary file head reading for type detection
47
+ - Directory walking with callback processing
48
+ - File comparison and relative path calculations
37
49
  - Comprehensive error handling with detailed context
38
50
 
39
51
  ### Encryptor
@@ -77,7 +89,8 @@ let appServerFactory = new AppServerFactory();
77
89
  let serverResult = appServerFactory.createAppServer({
78
90
  port: 3000,
79
91
  useHttps: false,
80
- autoListen: true
92
+ autoListen: true,
93
+ useCompression: true
81
94
  });
82
95
 
83
96
  if(serverResult){
@@ -183,7 +196,9 @@ let serverResult = appServerFactory.createAppServer({
183
196
  useVirtualHosts: true,
184
197
  keyPath: '/ssl/default.key',
185
198
  certPath: '/ssl/default.crt',
186
- port: 443
199
+ port: 443,
200
+ enforceProtocol: true,
201
+ developmentMultiplier: 10
187
202
  });
188
203
  ```
189
204
 
@@ -192,7 +207,7 @@ let serverResult = appServerFactory.createAppServer({
192
207
  ```javascript
193
208
  let appServerFactory = new AppServerFactory();
194
209
 
195
- // Add development domains
210
+ // Add development domains (automatically detected)
196
211
  appServerFactory.addDevelopmentDomain('localhost');
197
212
  appServerFactory.addDevelopmentDomain('dev.myapp.local');
198
213
 
@@ -200,6 +215,11 @@ let serverResult = appServerFactory.createAppServer({
200
215
  port: 3000,
201
216
  corsOrigin: ['http://localhost:3000', 'http://dev.myapp.local:3000'],
202
217
  developmentMultiplier: 5, // More lenient rate limiting in dev
218
+ developmentPorts: [3000, 3001, 8080],
219
+ developmentExternalDomains: {
220
+ 'script-src': ['https://cdn.example.com'],
221
+ 'style-src': ['https://fonts.googleapis.com']
222
+ }
203
223
  });
204
224
  ```
205
225
 
@@ -222,7 +242,9 @@ let serverResult = appServerFactory.createAppServer({
222
242
  globalRateLimit: 100, // requests per window
223
243
  windowMs: 60000, // 1 minute
224
244
  maxRequests: 30,
225
- trustedProxy: '127.0.0.1'
245
+ trustedProxy: '127.0.0.1',
246
+ useXssProtection: true,
247
+ sanitizeOptions: {allowedTags: [], allowedAttributes: {}}
226
248
  });
227
249
  ```
228
250
 
@@ -237,7 +259,6 @@ let serverResult = appServerFactory.createAppServer({
237
259
  - `enableServeHome(app, callback)` - Enables homepage serving
238
260
  - `serveStatics(app, staticPath)` - Serves static files
239
261
  - `serveStaticsPath(app, route, staticPath)` - Serves static files on specific route
240
- - `validateInput(input, type)` - Validates input against predefined patterns
241
262
  - `enableCSP(cspOptions)` - Enables Content Security Policy
242
263
  - `listen(port)` - Starts server listening
243
264
  - `close()` - Gracefully closes server
@@ -263,6 +284,13 @@ let serverResult = appServerFactory.createAppServer({
263
284
  - `generateSecureFilename(originalName)` - Generates cryptographically secure filename
264
285
  - `quarantineFile(path, reason)` - Moves file to quarantine folder
265
286
  - `createTempFile(prefix, extension)` - Creates a temporary file path
287
+ - `moveFile(from, to)` - Moves file to new location
288
+ - `getFileSize(path)` - Gets file size in bytes
289
+ - `compareFiles(file1, file2)` - Compares file contents
290
+ - `getRelativePath(from, to)` - Calculates relative path
291
+ - `walkDirectory(path, callback)` - Recursively processes directory tree
292
+ - `getDirectorySize(path)` - Calculates total directory size
293
+ - `emptyDirectory(path)` - Removes all contents from directory
266
294
 
267
295
  ### Encryptor Methods
268
296
 
@@ -284,6 +312,7 @@ let serverResult = appServerFactory.createAppServer({
284
312
  - `validateFilenameSecurity(filename)` - Validates filename for security
285
313
  - `validateFile(file, allowedType, callback)` - Validates a file during upload
286
314
  - `validateFileContents(file, allowedType)` - Validates file content after upload
315
+ - `convertToRegex(key)` - Converts MIME type patterns to regex
287
316
 
288
317
  ## Security Features
289
318
 
@@ -17,7 +17,7 @@ class CorsConfigurer
17
17
  this.corsMethods = ['GET','POST'];
18
18
  this.corsHeaders = ['Content-Type','Authorization'];
19
19
  this.developmentCorsOrigins = [];
20
- this.developmentPorts = [3000, 8080, 8081];
20
+ this.developmentPorts = [];
21
21
  }
22
22
 
23
23
  setup(app, config)
@@ -40,12 +40,15 @@ class CorsConfigurer
40
40
  allowedHeaders: this.corsHeaders,
41
41
  credentials: true
42
42
  };
43
+ if('*' === this.corsOrigin && true === corsOptions.credentials){
44
+ corsOptions.origin = true;
45
+ }
43
46
  if(this.isDevelopmentMode && 0 < this.developmentCorsOrigins.length){
44
47
  corsOptions.origin = (origin, callback) => {
45
48
  if(!origin){
46
49
  return callback(null, true);
47
50
  }
48
- if(-1 !== this.developmentCorsOrigins.indexOf(origin)){
51
+ if(-1 !== this.developmentCorsOrigins.indexOf(this.normalizeHost(origin))){
49
52
  return callback(null, true);
50
53
  }
51
54
  if('*' === this.corsOrigin){
@@ -59,20 +62,32 @@ class CorsConfigurer
59
62
 
60
63
  extractDevelopmentOrigins(domainMapping)
61
64
  {
62
- let origins = [];
65
+ let originsSet = new Set();
63
66
  let mappingKeys = Object.keys(domainMapping);
64
67
  for(let domain of mappingKeys){
65
- origins.push('http://'+domain);
66
- origins.push('https://'+domain);
67
- if(domain.includes(':')){
68
+ let normalizedDomain = this.normalizeHost(domain);
69
+ originsSet.add('http://'+normalizedDomain);
70
+ originsSet.add('https://'+normalizedDomain);
71
+ if(-1 !== normalizedDomain.indexOf(':')){
68
72
  continue;
69
73
  }
70
- for(let port of this.developmentPorts){
71
- origins.push('http://'+domain+':'+port);
72
- origins.push('https://'+domain+':'+port);
74
+ if(0 < this.developmentPorts.length){
75
+ for(let port of this.developmentPorts){
76
+ originsSet.add('http://'+normalizedDomain+':'+port);
77
+ originsSet.add('https://'+normalizedDomain+':'+port);
78
+ }
73
79
  }
74
80
  }
75
- return origins;
81
+ return Array.from(originsSet);
82
+ }
83
+
84
+ normalizeHost(originOrHost)
85
+ {
86
+ let normalizedHost = (''+originOrHost).toLowerCase();
87
+ if('/' === normalizedHost.charAt(normalizedHost.length - 1)){
88
+ normalizedHost = normalizedHost.substring(0, normalizedHost.length - 1);
89
+ }
90
+ return normalizedHost;
76
91
  }
77
92
 
78
93
  }
@@ -26,6 +26,7 @@ class DevelopmentModeDetector
26
26
  'staging.'
27
27
  ];
28
28
  this.developmentEnvironments = ['development', 'dev', 'test'];
29
+ this.env = process?.env?.NODE_ENV || 'production';
29
30
  }
30
31
 
31
32
  detect(config = {})
@@ -36,8 +37,7 @@ class DevelopmentModeDetector
36
37
  if(config.developmentEnvironments){
37
38
  this.developmentEnvironments = config.developmentEnvironments;
38
39
  }
39
- let env = process.env.NODE_ENV;
40
- if(this.developmentEnvironments.includes(env)){
40
+ if(this.developmentEnvironments.includes(this.env)){
41
41
  return true;
42
42
  }
43
43
  if(config.developmentDomains && 0 < config.developmentDomains.length){
@@ -49,7 +49,7 @@ class DevelopmentModeDetector
49
49
  }
50
50
  if(config.domains && 0 < config.domains.length){
51
51
  for(let domainConfig of config.domains){
52
- if(!domainConfig.hostname){
52
+ if(!domainConfig?.hostname){
53
53
  continue;
54
54
  }
55
55
  if(this.matchesPattern(domainConfig.hostname)){
@@ -62,8 +62,34 @@ class DevelopmentModeDetector
62
62
 
63
63
  matchesPattern(domain)
64
64
  {
65
- for(let pattern of this.developmentPatterns){
66
- if(domain.includes(pattern)){
65
+ if(!domain){
66
+ return false;
67
+ }
68
+ let d = (''+domain).toLowerCase();
69
+ if('localhost' === d){
70
+ return true;
71
+ }
72
+ if('127.0.0.1' === d){
73
+ return true;
74
+ }
75
+ if('::1' === d){
76
+ return true;
77
+ }
78
+ for(let pattern of this.developmentPatterns || []){
79
+ if(!pattern){
80
+ continue;
81
+ }
82
+ let p = (''+pattern).toLowerCase();
83
+ if('.' === p.charAt(0) && d.endsWith(p)){
84
+ return true;
85
+ }
86
+ if('.' === p.charAt(p.length - 1) && 0 === d.indexOf(p)){
87
+ return true;
88
+ }
89
+ if(d === p){
90
+ return true;
91
+ }
92
+ if(0 <= d.indexOf(p)){
67
93
  return true;
68
94
  }
69
95
  }
@@ -20,8 +20,8 @@ class ProtocolEnforcer
20
20
  this.useHttps = config.useHttps || false;
21
21
  this.enforceProtocol = config.enforceProtocol !== false;
22
22
  app.use((req, res, next) => {
23
- let protocol = req.get('X-Forwarded-Proto') || req.protocol;
24
- let host = req.get('host');
23
+ let protocol = (req.get('X-Forwarded-Proto') || req.protocol || '').toLowerCase();
24
+ let host = (req.get('host') || '').toLowerCase().trim();
25
25
  if(this.isDevelopmentMode){
26
26
  res.removeHeader('Origin-Agent-Cluster');
27
27
  res.removeHeader('Strict-Transport-Security');
@@ -29,12 +29,10 @@ class ProtocolEnforcer
29
29
  res.set('Origin-Agent-Cluster', '?0');
30
30
  if(this.enforceProtocol && host){
31
31
  if(!this.useHttps && 'https' === protocol){
32
- let redirectUrl = 'http://'+host+req.url;
33
- return res.redirect(301, redirectUrl);
32
+ return res.redirect(301, 'http://'+host+req.url);
34
33
  }
35
34
  if(this.useHttps && 'http' === protocol){
36
- let redirectUrl = 'https://'+host+req.url;
37
- return res.redirect(301, redirectUrl);
35
+ return res.redirect(301, 'https://'+host+req.url);
38
36
  }
39
37
  }
40
38
  }
@@ -25,9 +25,9 @@ class RateLimitConfigurer
25
25
  {
26
26
  this.isDevelopmentMode = config.isDevelopmentMode || false;
27
27
  this.globalRateLimit = config.globalRateLimit || 0;
28
- this.windowMs = config.windowMs || this.windowMs;
29
- this.maxRequests = config.maxRequests || this.maxRequests;
30
- this.developmentMultiplier = config.developmentMultiplier || this.developmentMultiplier;
28
+ this.windowMs = Number(config.windowMs || this.windowMs);
29
+ this.maxRequests = Number(config.maxRequests || this.maxRequests);
30
+ this.developmentMultiplier = Number(config.developmentMultiplier || this.developmentMultiplier);
31
31
  this.applyKeyGenerator = config.applyKeyGenerator || false;
32
32
  this.tooManyRequestsMessage = config.tooManyRequestsMessage || this.tooManyRequestsMessage;
33
33
  if(!this.globalRateLimit){
@@ -36,7 +36,7 @@ class RateLimitConfigurer
36
36
  let limiterParams = {
37
37
  windowMs: this.windowMs,
38
38
  limit: this.maxRequests,
39
- standardHeaders: true,
39
+ standardHeaders: 'draft-8',
40
40
  legacyHeaders: false,
41
41
  message: this.tooManyRequestsMessage
42
42
  };
@@ -56,7 +56,7 @@ class RateLimitConfigurer
56
56
  let limiterParams = {
57
57
  windowMs: this.windowMs,
58
58
  limit: this.maxRequests,
59
- standardHeaders: true,
59
+ standardHeaders: 'draft-8',
60
60
  legacyHeaders: false
61
61
  };
62
62
  if(this.isDevelopmentMode){
@@ -63,7 +63,10 @@ class SecurityConfigurer
63
63
  }
64
64
  };
65
65
  if(config.developmentExternalDomains){
66
- this.addExternalDomainsToCsp(helmetOptions.contentSecurityPolicy.directives, config.developmentExternalDomains);
66
+ this.addExternalDomainsToCsp(
67
+ helmetOptions.contentSecurityPolicy.directives,
68
+ config.developmentExternalDomains
69
+ );
67
70
  }
68
71
  return helmetOptions;
69
72
  }
@@ -99,7 +102,7 @@ class SecurityConfigurer
99
102
  if(!req.body){
100
103
  return next();
101
104
  }
102
- if('object' === typeof req.body){
105
+ if('object' === typeof req.body && null !== req.body){
103
106
  this.sanitizeRequestBody(req.body);
104
107
  }
105
108
  next();
@@ -108,9 +111,24 @@ class SecurityConfigurer
108
111
 
109
112
  sanitizeRequestBody(body)
110
113
  {
111
- let bodyKeys = Object.keys(body);
112
- for(let i = 0; i < bodyKeys.length; i++){
113
- let key = bodyKeys[i];
114
+ if(Array.isArray(body)){
115
+ for(let i = 0; i < body.length; i++){
116
+ if('string' === typeof body[i]){
117
+ body[i] = sanitizeHtml(body[i], this.sanitizeOptions);
118
+ continue;
119
+ }
120
+ if('object' === typeof body[i] && null !== body[i]){
121
+ this.sanitizeRequestBody(body[i]);
122
+ }
123
+ }
124
+ return;
125
+ }
126
+ if('object' !== typeof body || null === body){
127
+ return;
128
+ }
129
+ let keys = Object.keys(body);
130
+ for(let i = 0; i < keys.length; i++){
131
+ let key = keys[i];
114
132
  if('string' === typeof body[key]){
115
133
  body[key] = sanitizeHtml(body[key], this.sanitizeOptions);
116
134
  continue;
@@ -48,8 +48,8 @@ class AppServerFactory
48
48
  this.useXssProtection = true;
49
49
  this.globalRateLimit = 0;
50
50
  this.corsOrigin = '*';
51
- this.corsMethods = ['GET','POST'];
52
- this.corsHeaders = ['Content-Type','Authorization'];
51
+ this.corsMethods = [];
52
+ this.corsHeaders = [];
53
53
  this.tooManyRequestsMessage = 'Too many requests, please try again later.';
54
54
  this.error = {};
55
55
  this.processErrorResponse = false;
@@ -74,16 +74,9 @@ class AppServerFactory
74
74
  this.developmentDomains = [];
75
75
  this.domainMapping = {};
76
76
  this.enforceProtocol = true;
77
- this.developmentPatterns = [
78
- 'localhost',
79
- '127.0.0.1',
80
- '.local',
81
- '.test',
82
- '.dev',
83
- '.staging'
84
- ];
85
- this.developmentEnvironments = ['development', 'dev', 'test'];
86
- this.developmentPorts = [3000, 8080, 8081];
77
+ this.developmentPatterns = [];
78
+ this.developmentEnvironments = [];
79
+ this.developmentPorts = [];
87
80
  this.developmentMultiplier = 10;
88
81
  this.developmentExternalDomains = {};
89
82
  this.developmentModeDetector = new DevelopmentModeDetector();
@@ -133,10 +126,10 @@ class AppServerFactory
133
126
 
134
127
  extractDomainFromHttpUrl(url)
135
128
  {
136
- if(!url || (!url.startsWith('http://') && !url.startsWith('https://'))){
129
+ if(!url || !url.startsWith('http://')){
137
130
  return false;
138
131
  }
139
- return url.replace(/^https?:\/\//, '').split(':')[0];
132
+ return url.replace(/^http:\/\//, '').split(':')[0];
140
133
  }
141
134
 
142
135
  addHttpDomainsAsDevelopment()
@@ -153,12 +146,17 @@ class AppServerFactory
153
146
 
154
147
  detectDevelopmentMode()
155
148
  {
156
- this.isDevelopmentMode = this.developmentModeDetector.detect({
157
- developmentPatterns: this.developmentPatterns,
158
- developmentEnvironments: this.developmentEnvironments,
149
+ let detectConfig = {
159
150
  developmentDomains: this.developmentDomains,
160
151
  domains: this.domains
161
- });
152
+ };
153
+ if(0 < this.developmentPatterns.length){
154
+ detectConfig['developmentPatterns'] = this.developmentPatterns;
155
+ }
156
+ if(0 < this.developmentEnvironments.length){
157
+ detectConfig['developmentEnvironments'] = this.developmentEnvironments;
158
+ }
159
+ this.isDevelopmentMode = this.developmentModeDetector.detect(detectConfig);
162
160
  }
163
161
 
164
162
  setupDevelopmentConfiguration()
@@ -208,15 +206,28 @@ class AppServerFactory
208
206
 
209
207
  setupCors()
210
208
  {
211
- this.corsConfigurer.setup(this.app, {
209
+ let corsConfig = {
212
210
  isDevelopmentMode: this.isDevelopmentMode,
213
211
  useCors: this.useCors,
214
- corsOrigin: this.corsOrigin,
215
- corsMethods: this.corsMethods,
216
- corsHeaders: this.corsHeaders,
217
- domainMapping: this.domainMapping,
218
- developmentPorts: this.developmentPorts
219
- });
212
+ corsOrigin: this.corsOrigin
213
+ };
214
+ if(0 < this.corsMethods.length){
215
+ corsConfig['corsMethods'] = this.corsMethods;
216
+ }
217
+ if(0 < this.corsHeaders.length){
218
+ corsConfig['corsHeaders'] = this.corsHeaders;
219
+ }
220
+ if(0 < this.developmentPorts.length){
221
+ corsConfig['developmentPorts'] = this.developmentPorts;
222
+ }
223
+ if(
224
+ 'object' === typeof this.domainMapping
225
+ && null !== this.domainMapping
226
+ && 0 < Object.keys(this.domainMapping)
227
+ ){
228
+ corsConfig['domainMapping'] = this.domainMapping;
229
+ }
230
+ this.corsConfigurer.setup(this.app, corsConfig);
220
231
  }
221
232
 
222
233
  setupRateLimiting()
@@ -255,7 +266,7 @@ class AppServerFactory
255
266
  setupTrustedProxy()
256
267
  {
257
268
  if('' !== this.trustedProxy){
258
- this.app.enable('trust proxy', this.trustedProxy);
269
+ this.app.set('trust proxy', this.trustedProxy);
259
270
  }
260
271
  }
261
272
 
@@ -308,12 +319,13 @@ class AppServerFactory
308
319
  return false;
309
320
  }
310
321
  let cleanHostname = hostname.toLowerCase().trim();
322
+ let hostWithoutPort = cleanHostname.split(':')[0];
311
323
  for(let i = 0; i < this.domains.length; i++){
312
324
  let domain = this.domains[i];
313
- if(domain.hostname === cleanHostname){
325
+ if(domain.hostname === hostWithoutPort){
314
326
  return domain;
315
327
  }
316
- if(domain.aliases && domain.aliases.includes(cleanHostname)){
328
+ if(domain.aliases && domain.aliases.includes(hostWithoutPort)){
317
329
  return domain;
318
330
  }
319
331
  }
@@ -499,23 +511,6 @@ class AppServerFactory
499
511
  return this.securityConfigurer.enableCSP(this.app, cspOptions);
500
512
  }
501
513
 
502
- validateInput(input, type)
503
- {
504
- if('string' !== typeof input){
505
- return false;
506
- }
507
- let patterns = {
508
- email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
509
- username: /^[a-zA-Z0-9_-]{3,30}$/,
510
- strongPassword: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
511
- alphanumeric: /^[a-zA-Z0-9]+$/,
512
- numeric: /^\d+$/,
513
- hexColor: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
514
- ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
515
- };
516
- return patterns[type] ? patterns[type].test(input) : false;
517
- }
518
-
519
514
  }
520
515
 
521
516
  module.exports.AppServerFactory = AppServerFactory;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/server-utils",
3
3
  "scope": "@reldens",
4
- "version": "0.27.0",
4
+ "version": "0.29.0",
5
5
  "description": "Reldens - Server Utils",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -1,29 +0,0 @@
1
- --- a/lib/app-server-factory.js
2
- +++ b/lib/app-server-factory.js
3
- @@ -141,10 +141,14 @@ class AppServerFactory
4
-
5
- addHttpDomainsAsDevelopment()
6
- {
7
- + console.log('ENV VARS:', process.env.RELDENS_APP_HOST, process.env.RELDENS_PUBLIC_URL);
8
- let hostDomain = this.extractDomainFromHttpUrl(process.env.RELDENS_APP_HOST);
9
- let publicDomain = this.extractDomainFromHttpUrl(process.env.RELDENS_PUBLIC_URL);
10
- + console.log('EXTRACTED DOMAINS:', hostDomain, publicDomain);
11
- if(hostDomain && !this.developmentDomains.includes(hostDomain)){
12
- this.developmentDomains.push(hostDomain);
13
- + console.log('ADDED HOST DOMAIN:', hostDomain);
14
- }
15
- if(publicDomain && !this.developmentDomains.includes(publicDomain)){
16
- this.developmentDomains.push(publicDomain);
17
- + console.log('ADDED PUBLIC DOMAIN:', publicDomain);
18
- }
19
- + console.log('FINAL DEVELOPMENT DOMAINS:', this.developmentDomains);
20
- }
21
- @@ -154,6 +158,7 @@ class AppServerFactory
22
- this.isDevelopmentMode = this.developmentModeDetector.detect({
23
- developmentPatterns: this.developmentPatterns,
24
- developmentEnvironments: this.developmentEnvironments,
25
- developmentDomains: this.developmentDomains,
26
- domains: this.domains
27
- });
28
- + console.log('DEVELOPMENT MODE DETECTED:', this.isDevelopmentMode);
29
- }