@reldens/cms 0.14.0 → 0.16.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
@@ -18,8 +18,9 @@ A powerful, flexible Content Management System built with Node.js, featuring an
18
18
  - **Entity-based URLs** (e.g., `/articles/123`)
19
19
  - **Template fallback system** (domain → default → base)
20
20
  - **Layout system** with body content layouts and page wrappers
21
- - **Reusable content blocks** with `{{ entity() }}` template functions
22
- - **Collection rendering** with filtering and loop support
21
+ - **Reusable content blocks** with `<entity>` template functions
22
+ - **Collection rendering** with filtering, sorting, pagination, and loop support
23
+ - **Custom partial tags** with HTML-style syntax for complex partials
23
24
  - **Entity access control** for public/private content
24
25
  - **Static asset serving** with Express integration as default
25
26
  - **Template engine** with Mustache integration as default
@@ -41,7 +42,7 @@ A powerful, flexible Content Management System built with Node.js, featuring an
41
42
  - **Custom entity configuration** with validation rules
42
43
  - **Translation support** for entity labels and properties
43
44
  - **Content blocks management** via cms_blocks table
44
- - **Entity access control** via cms_entity_access table
45
+ - **Entity access control** via entities_access table
45
46
 
46
47
  ### - Configuration & Architecture
47
48
  - **Environment-based configuration** (.env file)
@@ -66,7 +67,7 @@ const cms = new Manager({
66
67
  projectRoot: process.cwd(),
67
68
  entityAccess: {
68
69
  cmsPages: { public: true, operations: ['read'] },
69
- products: { public: true, operations: ['read'] },
70
+ articles: { public: true, operations: ['read'] },
70
71
  users: { public: false }
71
72
  }
72
73
  });
@@ -128,43 +129,157 @@ const cms = new Manager({
128
129
  ## Enhanced Templating System
129
130
 
130
131
  ### Template Functions
131
- Templates support dynamic content blocks, entity rendering, and collections:
132
+ Templates support dynamic content blocks, entity rendering, and collections with advanced query options:
132
133
 
133
134
  **Single Entity Rendering:**
134
135
  ```html
135
136
  <!-- Render by any identifier field (like 'name' for content blocks) -->
136
- {{ entity('cmsBlocks', 'header-main', 'name') }}
137
- {{ entity('cmsBlocks', 'sidebar-left', 'name') }}
137
+ <entity name="cmsBlocks" field="name" value="header-main"/>
138
+ <entity name="cmsBlocks" field="name" value="sidebar-left"/>
138
139
 
139
140
  <!-- Render by ID (default identifier) -->
140
- {{ entity('products', '123') }}
141
- {{ entity('cmsPages', '1') }}
141
+ <entity name="articles" id="123"/>
142
+ <entity name="cmsPages" id="1"/>
142
143
  ```
143
144
 
144
145
  **Single Field Collections:**
145
146
  ```html
146
147
  <!-- Extract and concatenate a single field from multiple records -->
147
- {{ collection('cmsBlocks', {"status": "active", "category": "navigation"}, 'content') }}
148
- {{ collection('products', {"featured": true}, 'title') }}
148
+ <collection name="cmsBlocks" filters="{status: 'active', category: 'navigation'}" field="content"/>
149
+ <collection name="articles" filters="{featured: true}" field="title"/>
150
+
151
+ <!-- With query options for sorting and limiting -->
152
+ <collection name="articles" filters="{featured: true}" field="title" data="{limit: 5, sortBy: 'created_at', sortDirection: 'desc'}"/>
153
+ <collection name="cmsBlocks" filters="{status: 'active'}" field="content" data="{limit: 3, offset: 10, sortBy: 'priority'}"/>
149
154
  ```
150
155
 
151
156
  **Loop Collections:**
152
157
  ```html
153
158
  <!-- Loop through records with full template rendering -->
154
- {{ #collection('cmsBlocks', {"status": "active"}) }}
159
+ <collection name="cmsBlocks" filters="{status: 'active'}">
155
160
  <div class="block">
156
161
  <h3>{{row.title}}</h3>
157
162
  <div class="content">{{row.content}}</div>
158
163
  </div>
159
- {{ /collection('cmsBlocks') }}
164
+ </collection>
165
+
166
+ <collection name="articles" filters="{category: 'technology'}">
167
+ <div class="article">
168
+ <h4>{{row.title}}</h4>
169
+ <p>{{row.summary}}</p>
170
+ <img src="{{row.featured_image}}" alt="{{row.title}}">
171
+ </div>
172
+ </collection>
160
173
 
161
- {{ #collection('products', {"category": "electronics"}) }}
162
- <div class="product">
163
- <h4>{{row.name}}</h4>
164
- <p>Price: ${{row.price}}</p>
165
- <img src="{{row.image}}" alt="{{row.name}}">
174
+ <!-- With pagination and sorting -->
175
+ <collection name="articles" filters="{featured: true}" data="{limit: 10, offset: 0, sortBy: 'created_at', sortDirection: 'asc'}">
176
+ <div class="article-card">
177
+ <h4>{{row.title}}</h4>
178
+ <p>{{row.summary}}</p>
166
179
  </div>
167
- {{ /collection('products') }}
180
+ </collection>
181
+ ```
182
+
183
+ **Custom Partials with Variables:**
184
+
185
+ *New HTML-style partial tags:*
186
+ ```html
187
+ <!-- Clean HTML-style syntax for complex partials -->
188
+ <partial name="hero"
189
+ sectionStyle=" bg-black"
190
+ bigTextHtml="A free open-source platform to create multiplayer games!"
191
+ mediumTextHtml="Build with Node.js, MySQL, Colyseus, and Phaser 3"
192
+ htmlContentWrapper='<div class="d-lg-flex"><a href="/documentation" target="_blank" class="btn-get-started">Get Started!</a><a href="https://demo.reldens.com/" target="_blank" class="btn-watch-video"> Demo </a></div>'
193
+ imageUrl="/assets/web/reldens-check.png"
194
+ imageAlt="Reldens - MMORPG Platform" />
195
+
196
+ <!-- Self-closing and open/close syntax both supported -->
197
+ <partial name="productCard"
198
+ title="Premium Package"
199
+ price="$99"
200
+ highlighted="true">
201
+ </partial>
202
+ ```
203
+
204
+ *Traditional Mustache syntax is still supported:*
205
+ ```html
206
+ <!-- Call a partial with an inline JSON object -->
207
+ {{>hero -{
208
+ bigTextHtml: "A free open-source platform to create multiplayer games!",
209
+ mediumTextHtml: "Build with Node.js, MySQL, Colyseus, and Phaser 3",
210
+ imageUrl: "https://example.com/hero.jpg",
211
+ ctaText: "Get Started",
212
+ ctaLink: "/documentation"
213
+ }-}}
214
+
215
+ <!-- Call a partial within collections using row data -->
216
+ <collection name="cmsPages" filters="{featured: true}" data="{limit: 3}">
217
+ {{>cardView -{row}-}}
218
+ </collection>
219
+
220
+ <!-- Call a partial with mixed data -->
221
+ {{>productCard -{
222
+ title: "Premium Package",
223
+ price: "$99",
224
+ features: ["Advanced Analytics", "Priority Support", "Custom Themes"],
225
+ highlighted: true
226
+ }-}}
227
+ ```
228
+
229
+ **Example Partial Templates:**
230
+
231
+ **partials/hero.mustache:**
232
+ ```html
233
+ <section class="hero{{#sectionStyle}}{{sectionStyle}}{{/sectionStyle}}">
234
+ <div class="hero-content">
235
+ <h1>{{&bigTextHtml}}</h1>
236
+ <p>{{&mediumTextHtml}}</p>
237
+ {{#htmlContentWrapper}}
238
+ {{&htmlContentWrapper}}
239
+ {{/htmlContentWrapper}}
240
+ {{#imageUrl}}
241
+ <img src="{{imageUrl}}" alt="{{imageAlt}}" class="hero-image">
242
+ {{/imageUrl}}
243
+ </div>
244
+ </section>
245
+ ```
246
+
247
+ **partials/cardView.mustache:**
248
+ ```html
249
+ <div class="card">
250
+ <h3>{{title}}</h3>
251
+ <p>{{json_data.excerpt}}</p>
252
+ {{#json_data.featured_image}}
253
+ <img src="{{json_data.featured_image}}" alt="{{title}}">
254
+ {{/json_data.featured_image}}
255
+ {{#json_data.cta_text}}
256
+ <a href="{{json_data.cta_link}}" class="btn">{{json_data.cta_text}}</a>
257
+ {{/json_data.cta_text}}
258
+ </div>
259
+ ```
260
+
261
+ ### Collection Query Options
262
+ Collections support advanced query parameters for pagination and sorting:
263
+
264
+ - **limit** - Maximum number of records to return
265
+ - **offset** - Number of records to skip (for pagination)
266
+ - **sortBy** - Field name to sort by
267
+ - **sortDirection** - Sort direction ('asc' or 'desc')
268
+
269
+ **Examples:**
270
+ ```html
271
+ <!-- Get the first 5 articles sorted by title -->
272
+ <collection name="articles" filters="{}" field="title" data="{limit: 5, sortBy: 'title'}"/>
273
+
274
+ <!-- Paginated results: skip first 20, get next 10 -->
275
+ <collection name="articles" filters="{published: true}" data="{limit: 10, offset: 20, sortBy: 'created_at', sortDirection: 'desc'}">
276
+ <article>{{row.title}}</article>
277
+ </collection>
278
+
279
+ <!-- The latest 3 featured articles by creation date -->
280
+ <collection name="articles" filters="{featured: true}" data="{limit: 3, sortBy: 'created_at', sortDirection: 'desc'}">
281
+ <div class="featured-article">{{row.title}}</div>
282
+ </collection>
168
283
  ```
169
284
 
170
285
  ### Layout System
@@ -180,7 +295,7 @@ The CMS uses a two-tier layout system:
180
295
  <link href="/css/styles.css" rel="stylesheet"/>
181
296
  </head>
182
297
  <body class="{{siteHandle}}">
183
- {{{content}}}
298
+ {{&content}}
184
299
  <script src="/js/scripts.js"></script>
185
300
  </body>
186
301
  </html>
@@ -188,22 +303,22 @@ The CMS uses a two-tier layout system:
188
303
 
189
304
  **layouts/default.html** - Body content only:
190
305
  ```html
191
- {{ entity('cmsBlocks', 'header-main', 'name') }}
306
+ <entity name="cmsBlocks" field="name" value="header-main"/>
192
307
 
193
308
  <main id="main" class="main-container">
194
309
  <div class="container">
195
310
  <div class="row">
196
311
  <div class="col-md-3">
197
- {{ entity('cmsBlocks', 'sidebar-left', 'name') }}
312
+ <entity name="cmsBlocks" field="name" value="sidebar-left"/>
198
313
  </div>
199
314
  <div class="col-md-9">
200
- {{{content}}}
315
+ {{&content}}
201
316
  </div>
202
317
  </div>
203
318
  </div>
204
319
  </main>
205
320
 
206
- {{ entity('cmsBlocks', 'footer-main', 'name') }}
321
+ <entity name="cmsBlocks" field="name" value="footer-main"/>
207
322
  ```
208
323
 
209
324
  Pages can use different layouts by setting the `layout` field in `cms_pages`:
@@ -212,12 +327,12 @@ Pages can use different layouts by setting the `layout` field in `cms_pages`:
212
327
  - `minimal` - Basic layout with minimal styling
213
328
 
214
329
  ### Content Blocks
215
- Create reusable content blocks in the `cms_blocks` table via admin panel:
330
+ Create reusable content blocks in the `cms_blocks` table via the admin panel:
216
331
  ```sql
217
332
  INSERT INTO cms_blocks (name, title, content) VALUES
218
333
  ('contact-info', 'Contact Information', '<p>Email: info@example.com</p>'),
219
- ('product-sidebar', 'Product Categories',
220
- '<div class="categories"><h3>Categories</h3><ul><li><a href="/products/electronics">Electronics</a></li></ul></div>');
334
+ ('article-sidebar', 'Article Categories',
335
+ '<div class="categories"><h3>Categories</h3><ul><li><a href="/articles/technology">Technology</a></li></ul></div>');
221
336
  ```
222
337
 
223
338
  ### Entity Access Control
@@ -225,7 +340,7 @@ Control which entities are publicly accessible:
225
340
  ```javascript
226
341
  const cms = new Manager({
227
342
  entityAccess: {
228
- products: { public: true, operations: ['read'] },
343
+ articles: { public: true, operations: ['read'] },
229
344
  cmsPages: { public: true, operations: ['read'] },
230
345
  users: { public: false }
231
346
  }
@@ -310,7 +425,7 @@ cms.events.on('adminEntityExtraData', ({entitySerializedData, entity}) => {
310
425
  - `routes` - URL routing and SEO metadata
311
426
  - `cms_pages` - Page content with layout assignments
312
427
  - `cms_blocks` - Reusable content blocks
313
- - `cms_entity_access` - Entity access control rules
428
+ - `entities_access` - Entity access control rules
314
429
  - `entities_meta` - Generic metadata storage
315
430
  - `cms_pages_meta` - Page-specific metadata
316
431
 
@@ -331,18 +446,19 @@ The installer provides checkboxes for:
331
446
  - `initializeServices()` - Initialize all services
332
447
 
333
448
  ### Frontend Class
334
- - `initialize()` - Setup frontend routes and templates
449
+ - `initialize()` - Set up frontend routes and templates
335
450
  - `handleRequest(req, res)` - Main request handler
336
451
  - `findRouteByPath(path)` - Database route lookup
337
452
  - `findEntityByPath(path)` - Entity-based URL handling
338
453
 
339
454
  ### TemplateEngine Class
340
455
  - `render(template, data, partials)` - Main template rendering with enhanced functions
341
- - `processEntityFunctions(template)` - Process {{ entity() }} functions
342
- - `processSingleFieldCollections(template)` - Process single field collections
343
- - `processLoopCollections(template)` - Process loop collections
456
+ - `processEntityFunctions(template)` - Process `<entity>` functions
457
+ - `processSingleFieldCollections(template)` - Process single field collections with query options
458
+ - `processLoopCollections(template)` - Process loop collections with query options
459
+ - `processCustomPartials(template)` - Process `<partial>` tags with attribute parsing
344
460
  - `fetchEntityForTemplate(tableName, identifier, identifierField)` - Load single entity
345
- - `fetchCollectionForTemplate(tableName, filtersJson)` - Load entity collections
461
+ - `fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson)` - Load entity collections with pagination and sorting
346
462
 
347
463
  ### AdminManager Class
348
464
  - `setupAdmin()` - Initialize admin panel
@@ -1,8 +1,7 @@
1
1
  /*
2
- * Reldens - CMS
2
+ * Reldens - CMS - Administration Panel
3
3
  */
4
4
 
5
-
6
5
  :root {
7
6
  --normalFont: Verdana, Geneva, sans-serif;
8
7
  --reldensFont: "Play", sans-serif;
@@ -163,7 +162,7 @@
163
162
  padding: 1rem;
164
163
  overflow: auto;
165
164
  cursor: pointer;
166
- background-color: #fff;
165
+ background-color: var(--white);
167
166
  border-radius: 8px;
168
167
 
169
168
  & canvas {
@@ -241,7 +240,7 @@
241
240
  text-decoration: none;
242
241
  cursor: pointer;
243
242
  font-size: 14px;
244
- font-weight: 400;
243
+ font-weight: var(--font-semi-bold);
245
244
  border-bottom: 1px solid var(--darkBlue);
246
245
 
247
246
  &:hover {
@@ -314,6 +313,9 @@
314
313
  }
315
314
 
316
315
  & textarea {
316
+ width: 98%;
317
+ margin: 0;
318
+ padding: 1%;
317
319
  resize: vertical;
318
320
  min-height: 300px;
319
321
  }
@@ -401,30 +403,30 @@
401
403
  padding: 0 1rem;
402
404
  color: var(--black);
403
405
  }
406
+ }
404
407
 
405
- &-edit, &-delete {
406
- & span {
407
- display: block;
408
- text-align: center;
409
- cursor: pointer;
410
- }
408
+ & .field-edit, & .field-delete {
409
+ & span {
410
+ display: block;
411
+ text-align: center;
412
+ cursor: pointer;
411
413
  }
414
+ }
412
415
 
413
- &-edit {
414
- & span {
415
- & svg, path {
416
- width: 24px;
417
- color: var(--lightBlue);
418
- }
416
+ & .field-edit {
417
+ & span {
418
+ & svg, path {
419
+ width: 24px;
420
+ color: var(--lightBlue);
419
421
  }
420
422
  }
423
+ }
421
424
 
422
- &-delete {
423
- & span {
424
- & svg, path {
425
- width: 24px;
426
- fill: var(--lightBlue);
427
- }
425
+ & .field-delete {
426
+ & span {
427
+ & svg, path {
428
+ width: 24px;
429
+ fill: var(--lightBlue);
428
430
  }
429
431
  }
430
432
  }
@@ -438,6 +440,7 @@
438
440
  display: flex;
439
441
  flex-wrap: wrap;
440
442
  justify-content: flex-start;
443
+ margin-top: 1rem;
441
444
 
442
445
  &.hidden {
443
446
  display: none;
@@ -532,6 +535,8 @@
532
535
  color: var(--darkGrey);
533
536
 
534
537
  &.filters-toggle {
538
+ margin-bottom: 0;
539
+
535
540
  & img {
536
541
  max-width: 30px;
537
542
  margin-right: 1rem;
@@ -784,7 +789,7 @@
784
789
  &:not([disabled]) {
785
790
  margin: 0;
786
791
  border: 1px solid #7f8c8d;
787
- background: #fff;
792
+ background: var(--white);
788
793
 
789
794
  &[type="checkbox"] {
790
795
  max-width: max-content;
@@ -59,6 +59,12 @@ window.addEventListener('DOMContentLoaded', () => {
59
59
  errorId: 'Missing entity ID on POST.'
60
60
  };
61
61
 
62
+ // copyright year:
63
+ let copyRightYear = document.querySelector('.copyright-year');
64
+ if(copyRightYear){
65
+ copyRightYear.innerHTML = String((new Date()).getFullYear());
66
+ }
67
+
62
68
  // activate expand/collapse elements
63
69
  let expandCollapseButtons = document.querySelectorAll('[data-expand-collapse]');
64
70
  if(expandCollapseButtons){
@@ -170,6 +176,7 @@ window.addEventListener('DOMContentLoaded', () => {
170
176
  let filtersToggleContent = document.querySelector('.filters-toggle-content');
171
177
  if(filtersToggle && filtersToggleContent){
172
178
  filtersToggle.addEventListener('click', () => {
179
+ filtersToggle.classList.toggle('active');
173
180
  filtersToggleContent.classList.toggle('hidden');
174
181
  });
175
182
  let allFilters = document.querySelectorAll('.filters-toggle-content .filter input');
@@ -14,8 +14,10 @@
14
14
  <span class="field-value">{{&value}}</span>
15
15
  </div>
16
16
  {{/editFields}}
17
+ {{&extraFormContent}}
17
18
  <div class="actions">
18
- <input class="button button-primary button-submit" type="submit" value="Save"/>
19
+ <button class="button button-primary button-submit" type="submit" name="saveAction" value="save">Save</button>
20
+ <button class="button button-primary button-submit" type="submit" name="saveAction" value="saveAndContinue">Save and continue...</button>
19
21
  <a class="button button-secondary button-back" href="{{&entityViewRoute}}">Cancel</a>
20
22
  </div>
21
23
  </form>
@@ -0,0 +1 @@
1
+ <textarea name="{{&fieldName}}" id="{{&fieldName}}" disabled="disabled">{{&fieldValue}}</textarea>
@@ -0,0 +1,15 @@
1
+ <fieldset class="cms-page-route">
2
+ <legend>Route data</legend>
3
+ <div class="edit-field">
4
+ <span class="field-name">Path</span>
5
+ <span class="field-value">
6
+ <input type="text" name="routePath" value="{{routePath}}"/>
7
+ </span>
8
+ </div>
9
+ <div class="edit-field">
10
+ <span class="field-name">Domain</span>
11
+ <span class="field-value">
12
+ <input type="text" name="routeDomain" value="{{routeDomain}}"/>
13
+ </span>
14
+ </div>
15
+ </fieldset>
@@ -0,0 +1,15 @@
1
+ <fieldset class="cms-page-route">
2
+ <legend>Route data</legend>
3
+ <div class="edit-field">
4
+ <span class="field-name">Path</span>
5
+ <span class="field-value">
6
+ <input type="text" name="route-path" value="{{routePath}}" disabled="disabled"/>
7
+ </span>
8
+ </div>
9
+ <div class="edit-field">
10
+ <span class="field-name">Domain</span>
11
+ <span class="field-value">
12
+ <input type="text" name="route-domain" value="{{routeDomain}}" disabled="disabled"/>
13
+ </span>
14
+ </div>
15
+ </fieldset>
@@ -6,6 +6,7 @@
6
6
  <span class="field-value">{{&value}}</span>
7
7
  </div>
8
8
  {{/fields}}
9
+ {{&extraFormContent}}
9
10
  <div class="actions">
10
11
  <a class="button button-primary" href="{{&entityNewRoute}}">Create New</a>
11
12
  <a class="button button-primary" href="{{&entityEditRoute}}">Edit</a>
@@ -8,6 +8,7 @@
8
8
 
9
9
  const { Manager } = require('../index');
10
10
  const { Logger } = require('@reldens/utils');
11
+ const { FileHandler } = require('@reldens/server-utils');
11
12
  const readline = require('readline');
12
13
 
13
14
  class CmsEntitiesGenerator
@@ -18,6 +19,17 @@ class CmsEntitiesGenerator
18
19
  this.args = process.argv.slice(2);
19
20
  this.projectRoot = process.cwd();
20
21
  this.isOverride = this.args.includes('--override');
22
+ this.prismaClientPath = this.extractArgument('--prisma-client');
23
+ this.driver = this.extractArgument('--driver') || process.env.RELDENS_STORAGE_DRIVER || 'prisma';
24
+ }
25
+
26
+ extractArgument(argumentName)
27
+ {
28
+ let argIndex = this.args.indexOf(argumentName);
29
+ if(-1 === argIndex || argIndex + 1 >= this.args.length){
30
+ return null;
31
+ }
32
+ return this.args[argIndex + 1];
21
33
  }
22
34
 
23
35
  async run()
@@ -29,14 +41,21 @@ class CmsEntitiesGenerator
29
41
  return false;
30
42
  }
31
43
  }
32
- let manager = new Manager({projectRoot: this.projectRoot});
44
+ let managerConfig = {projectRoot: this.projectRoot};
45
+ if('prisma' === this.driver){
46
+ let prismaClient = await this.loadPrismaClient();
47
+ if(prismaClient){
48
+ managerConfig.prismaClient = prismaClient;
49
+ }
50
+ }
51
+ let manager = new Manager(managerConfig);
33
52
  if(!manager.isInstalled()){
34
53
  Logger.error('CMS is not installed. Please run installation first.');
35
54
  return false;
36
55
  }
37
56
  Logger.debug('Reldens CMS Manager instance created for entities generation.');
38
57
  await manager.initializeDataServer();
39
- let success = await manager.installer.generateEntities(manager.dataServer, this.isOverride);
58
+ let success = await manager.installer.generateEntities(manager.dataServer, this.isOverride, false);
40
59
  if(!success){
41
60
  Logger.error('Entities generation failed.');
42
61
  return false;
@@ -45,6 +64,32 @@ class CmsEntitiesGenerator
45
64
  return true;
46
65
  }
47
66
 
67
+ async loadPrismaClient()
68
+ {
69
+ let clientPath = this.prismaClientPath;
70
+ if(!clientPath){
71
+ clientPath = FileHandler.joinPaths(process.cwd(), 'generated-entities', 'prisma');
72
+ }
73
+ if(!FileHandler.exists(clientPath)){
74
+ Logger.error('Prisma client not found at: '+clientPath);
75
+ Logger.error('Please ensure the client exists or specify a custom path with --prisma-client');
76
+ return false;
77
+ }
78
+ try {
79
+ let PrismaClientModule = require(clientPath);
80
+ let PrismaClient = PrismaClientModule.PrismaClient || PrismaClientModule.default?.PrismaClient;
81
+ if(!PrismaClient){
82
+ Logger.error('PrismaClient not found in module: '+clientPath);
83
+ return false;
84
+ }
85
+ Logger.debug('Prisma client loaded from: '+clientPath);
86
+ return new PrismaClient();
87
+ } catch (error) {
88
+ Logger.error('Failed to load Prisma client from '+clientPath+': '+error.message);
89
+ return false;
90
+ }
91
+ }
92
+
48
93
  async confirmOverride()
49
94
  {
50
95
  let rl = readline.createInterface({
@@ -59,7 +104,6 @@ class CmsEntitiesGenerator
59
104
  });
60
105
  });
61
106
  }
62
-
63
107
  }
64
108
 
65
109
  let generator = new CmsEntitiesGenerator();
@@ -8,14 +8,37 @@
8
8
 
9
9
  const { Manager } = require('../index');
10
10
  const { Logger } = require('@reldens/utils');
11
+ const { FileHandler } = require('@reldens/server-utils');
11
12
 
12
13
  let args = process.argv.slice(2);
13
14
  let projectRoot = args[0] || process.cwd();
15
+ let indexPath = FileHandler.joinPaths(projectRoot, 'index.js');
14
16
 
15
- let manager = new Manager({projectRoot});
17
+ if(FileHandler.exists(indexPath)){
18
+ require(indexPath);
19
+ return;
20
+ }
21
+
22
+ let managerConfig = {projectRoot};
23
+ let entitiesPath = FileHandler.joinPaths(
24
+ projectRoot,
25
+ 'generated-entities',
26
+ 'models',
27
+ 'prisma',
28
+ 'registered-models-prisma.js'
29
+ );
30
+
31
+ if(FileHandler.exists(entitiesPath)){
32
+ let entitiesModule = require(entitiesPath);
33
+ managerConfig.rawRegisteredEntities = entitiesModule.rawRegisteredEntities;
34
+ managerConfig.entitiesConfig = entitiesModule.entitiesConfig;
35
+ managerConfig.entitiesTranslations = entitiesModule.entitiesTranslations;
36
+ }
37
+
38
+ let manager = new Manager(managerConfig);
16
39
  Logger.debug('Reldens CMS Manager instance created.', {configuration: manager.config});
17
40
 
18
- let started = manager.start().then((result) => {
41
+ manager.start().then((result) => {
19
42
  if(!result){
20
43
  Logger.info('Reldens CMS started by command failed.');
21
44
  return false;
@@ -25,9 +48,4 @@ let started = manager.start().then((result) => {
25
48
  }).catch((error) => {
26
49
  Logger.critical('Failed to start CMS:', error);
27
50
  process.exit();
28
- });
29
-
30
- if(!started){
31
- Logger.error('Reldens CMS start process failed.');
32
- process.exit();
33
- }
51
+ });
@@ -29,4 +29,9 @@ document.addEventListener('DOMContentLoaded', function() {
29
29
  installButton.disabled = true;
30
30
  }
31
31
  });
32
+
33
+ let copyRightYear = document.querySelector('.copyright-year');
34
+ if(copyRightYear){
35
+ copyRightYear.innerHTML = String((new Date()).getFullYear());
36
+ }
32
37
  });
@@ -29,7 +29,7 @@
29
29
  </div>
30
30
  <div class="footer">
31
31
  <div class="copyright">
32
- &copy;{{currentYear}} Reldens CMS
32
+ &copy;<span class="copyright-year">2025</span> Reldens CMS
33
33
  </div>
34
34
  </div>
35
35
  </div>