@reldens/cms 0.65.0 → 0.67.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/.claude/api-reference.md +30 -5
- package/.claude/architecture-guide.md +11 -5
- package/.claude/installation-guide.md +11 -9
- package/bin/reldens-cms-generate-entities.js +1 -1
- package/bin/reldens-cms-update-password.js +1 -1
- package/bin/reldens-cms.js +31 -48
- package/lib/admin-manager/router-contents.js +5 -1
- package/lib/manager-component-validator.js +72 -0
- package/lib/manager-config-loader.js +33 -0
- package/lib/manager-services-initializer.js +323 -0
- package/lib/manager.js +28 -386
- package/lib/mysql-installer.js +104 -108
- package/lib/prisma-subprocess-worker.js +8 -7
- package/package.json +3 -3
- package/.claude/hook-approvals.json +0 -1
package/.claude/api-reference.md
CHANGED
|
@@ -4,14 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
- `start()` - Initialize and start the CMS
|
|
6
6
|
- `isInstalled()` - Check if CMS is installed
|
|
7
|
-
- `initializeServices()` - Initialize all services
|
|
8
|
-
- `validateProvidedServer()` - Validate provided server instance
|
|
9
|
-
- `validateProvidedDataServer()` - Validate provided data server
|
|
10
|
-
- `validateProvidedAdminManager()` - Validate provided admin manager
|
|
11
|
-
- `validateProvidedFrontend()` - Validate provided frontend
|
|
12
7
|
- `buildAppServerConfiguration()` - Build server configuration
|
|
8
|
+
- `validateCdnMappingsInDevelopment()` - Warn when CDN domains are missing from CSP directives
|
|
9
|
+
- `isCdnUrlInDirectives(cdnUrlWithProtocol, cdnHostname)` - Check if a CDN URL appears in any CSP directive
|
|
13
10
|
- `initializeCmsAfterInstall(props)` - Post-installation callback
|
|
14
11
|
|
|
12
|
+
## ManagerComponentValidator Class (lib/manager-component-validator.js)
|
|
13
|
+
|
|
14
|
+
Static validators called from the Manager constructor:
|
|
15
|
+
|
|
16
|
+
- `validateProvidedServer(app, appServer)` - Validate provided Express app and server
|
|
17
|
+
- `validateProvidedDataServer(dataServer)` - Validate provided data server
|
|
18
|
+
- `validateProvidedAdminManager(adminManager)` - Validate provided admin manager
|
|
19
|
+
- `validateProvidedFrontend(frontend)` - Validate provided frontend
|
|
20
|
+
|
|
21
|
+
## ManagerConfigLoader Class (lib/manager-config-loader.js)
|
|
22
|
+
|
|
23
|
+
- `loadFromEnv()` - Build config object from `RELDENS_*` environment variables
|
|
24
|
+
|
|
25
|
+
## ManagerServicesInitializer Class (lib/manager-services-initializer.js)
|
|
26
|
+
|
|
27
|
+
Receives the Manager instance via constructor and handles all service initialization:
|
|
28
|
+
|
|
29
|
+
- `initializeServices()` - Orchestrate full service initialization sequence
|
|
30
|
+
- `initializeDataServer()` - Create data server; auto-creates PrismaClient via PrismaClientLoader when driver is `prisma` and no client provided
|
|
31
|
+
- `setupEntityAccess()` - Sync entity access rules to database
|
|
32
|
+
- `loadProcessedEntities()` - Apply config overrides and process raw entities
|
|
33
|
+
- `generateAdminEntities()` - Generate admin panel entity definitions
|
|
34
|
+
- `initializeAdminManager()` - Set up admin routes and authentication
|
|
35
|
+
- `initializePasswordEncryptionHandler()` - Register password encryption event listeners
|
|
36
|
+
- `initializeCmsPagesRouteManager()` - Wire CMS page routes to the data server
|
|
37
|
+
- `initializeFrontend()` - Create and initialize Frontend instance
|
|
38
|
+
- `renderCallback(template, params)` - Render a template via the configured render engine
|
|
39
|
+
|
|
15
40
|
## Installer Class
|
|
16
41
|
|
|
17
42
|
- `isInstalled()` - Check installation status
|
|
@@ -8,14 +8,20 @@
|
|
|
8
8
|
|
|
9
9
|
Main CMS orchestrator that:
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- Initializes password encryption handler
|
|
11
|
+
- Coordinates service lifecycle and startup
|
|
12
|
+
- Manages multi-domain setup and CDN validation
|
|
13
|
+
- Builds app server configuration
|
|
14
|
+
- Delegates service initialization to `ManagerServicesInitializer`
|
|
16
15
|
- Defines default templateExtensions: `['.html', '.mustache', '.template', '.txt', '.xml', '.json']`
|
|
17
16
|
- Passes templateExtensions to all frontend components ensuring consistency
|
|
18
17
|
|
|
18
|
+
Responsibilities are split across four files:
|
|
19
|
+
|
|
20
|
+
- `lib/manager.js` — orchestration, server config, lifecycle
|
|
21
|
+
- `lib/manager-component-validator.js` — static validation of provided server/dataServer/adminManager/frontend instances
|
|
22
|
+
- `lib/manager-config-loader.js` — static `loadFromEnv()` reading all `RELDENS_*` env vars into a config object
|
|
23
|
+
- `lib/manager-services-initializer.js` — data server, admin, frontend, entity and route manager initialization; auto-creates `PrismaClient` via `PrismaClientLoader` from `@reldens/storage` when driver is `prisma` and no client is provided
|
|
24
|
+
|
|
19
25
|
### Frontend
|
|
20
26
|
|
|
21
27
|
**File:** `lib/frontend.js`
|
|
@@ -6,12 +6,13 @@ The installer supports complex operations through subprocess management:
|
|
|
6
6
|
|
|
7
7
|
```javascript
|
|
8
8
|
const { Installer } = require('@reldens/cms');
|
|
9
|
+
const { Logger } = require('@reldens/utils');
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
let installer = new Installer({
|
|
11
12
|
projectRoot: process.cwd(),
|
|
12
13
|
subprocessMaxAttempts: 1800,
|
|
13
14
|
postInstallCallback: async (props) => {
|
|
14
|
-
|
|
15
|
+
Logger.info('Entities loaded: '+Object.keys(props.loadedEntities.rawRegisteredEntities).length);
|
|
15
16
|
return true;
|
|
16
17
|
}
|
|
17
18
|
});
|
|
@@ -31,7 +32,7 @@ const installer = new Installer({
|
|
|
31
32
|
The Manager class provides comprehensive service initialization:
|
|
32
33
|
|
|
33
34
|
```javascript
|
|
34
|
-
|
|
35
|
+
let cms = new Manager({
|
|
35
36
|
app: customExpressApp,
|
|
36
37
|
appServer: customAppServer,
|
|
37
38
|
dataServer: customDataServer,
|
|
@@ -54,6 +55,7 @@ const cms = new Manager({
|
|
|
54
55
|
|
|
55
56
|
- Validates all provided instances
|
|
56
57
|
- Initializes missing services
|
|
58
|
+
- Auto-creates `PrismaClient` via `PrismaClientLoader` from `@reldens/storage` when `RELDENS_STORAGE_DRIVER=prisma` and no `prismaClient` is passed in — no Prisma imports needed in your entry point
|
|
57
59
|
- Sets up entity access control
|
|
58
60
|
- Generates admin entities
|
|
59
61
|
- Configures template reloading
|
|
@@ -65,7 +67,7 @@ The CMS automatically detects development environments based on domain patterns.
|
|
|
65
67
|
**Default Development Patterns:**
|
|
66
68
|
|
|
67
69
|
```javascript
|
|
68
|
-
|
|
70
|
+
let patterns = [
|
|
69
71
|
'localhost',
|
|
70
72
|
'127.0.0.1',
|
|
71
73
|
'.local',
|
|
@@ -84,7 +86,7 @@ const patterns = [
|
|
|
84
86
|
**Override Development Patterns:**
|
|
85
87
|
|
|
86
88
|
```javascript
|
|
87
|
-
|
|
89
|
+
let cms = new Manager({
|
|
88
90
|
developmentPatterns: [
|
|
89
91
|
'localhost',
|
|
90
92
|
'127.0.0.1',
|
|
@@ -109,7 +111,7 @@ const cms = new Manager({
|
|
|
109
111
|
Configure external domains for CSP directives (kebab-case or camelCase):
|
|
110
112
|
|
|
111
113
|
```javascript
|
|
112
|
-
|
|
114
|
+
let cms = new Manager({
|
|
113
115
|
appServerConfig: {
|
|
114
116
|
developmentExternalDomains: {
|
|
115
117
|
'scriptSrc': ['https://cdn.example.com'],
|
|
@@ -131,7 +133,7 @@ const cms = new Manager({
|
|
|
131
133
|
**Default (merge with base directives):**
|
|
132
134
|
|
|
133
135
|
```javascript
|
|
134
|
-
|
|
136
|
+
let cms = new Manager({
|
|
135
137
|
appServerConfig: {
|
|
136
138
|
helmetConfig: {
|
|
137
139
|
contentSecurityPolicy: {
|
|
@@ -165,7 +167,7 @@ const cms = new Manager({
|
|
|
165
167
|
**Complete Replacement:**
|
|
166
168
|
|
|
167
169
|
```javascript
|
|
168
|
-
|
|
170
|
+
let cms = new Manager({
|
|
169
171
|
appServerConfig: {
|
|
170
172
|
helmetConfig: {
|
|
171
173
|
contentSecurityPolicy: {
|
|
@@ -190,7 +192,7 @@ const cms = new Manager({
|
|
|
190
192
|
### Additional Helmet Security Headers
|
|
191
193
|
|
|
192
194
|
```javascript
|
|
193
|
-
|
|
195
|
+
let cms = new Manager({
|
|
194
196
|
appServerConfig: {
|
|
195
197
|
helmetConfig: {
|
|
196
198
|
hsts: {
|
package/bin/reldens-cms.js
CHANGED
|
@@ -9,30 +9,24 @@
|
|
|
9
9
|
const { Manager } = require('../index');
|
|
10
10
|
const { Logger } = require('@reldens/utils');
|
|
11
11
|
const { FileHandler } = require('@reldens/server-utils');
|
|
12
|
-
const
|
|
12
|
+
const { PrismaClientLoader } = require('@reldens/storage');
|
|
13
|
+
const readline = require('readline/promises');
|
|
13
14
|
const dotenv = require('dotenv');
|
|
14
15
|
|
|
15
16
|
let args = process.argv.slice(2);
|
|
16
17
|
let projectRoot = args[0] || process.cwd();
|
|
17
18
|
let indexPath = FileHandler.joinPaths(projectRoot, 'index.js');
|
|
18
19
|
|
|
19
|
-
if(FileHandler.exists(indexPath)){
|
|
20
|
-
require(indexPath);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
async function checkRequiredPackages(projectRoot)
|
|
25
21
|
{
|
|
26
22
|
let requiredPackages = ['@reldens/cms'];
|
|
27
23
|
let missingPackages = [];
|
|
28
24
|
for(let packageName of requiredPackages){
|
|
29
|
-
|
|
30
|
-
if(!FileHandler.exists(packagePath)){
|
|
25
|
+
if(!FileHandler.exists(FileHandler.joinPaths(projectRoot, 'node_modules', packageName))){
|
|
31
26
|
missingPackages.push(packageName);
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
if(!FileHandler.exists(packagePath)){
|
|
29
|
+
if(!FileHandler.exists(FileHandler.joinPaths(projectRoot, 'node_modules', '@prisma/client'))){
|
|
36
30
|
missingPackages.push('@prisma/client');
|
|
37
31
|
}
|
|
38
32
|
return missingPackages;
|
|
@@ -44,14 +38,11 @@ async function promptUserConfirmation(packages)
|
|
|
44
38
|
input: process.stdin,
|
|
45
39
|
output: process.stdout
|
|
46
40
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
53
|
-
});
|
|
54
|
-
});
|
|
41
|
+
Logger.info('Missing required packages: '+packages.join(', '));
|
|
42
|
+
Logger.info('These packages are required for the CMS to function properly.');
|
|
43
|
+
let answer = await rl.question('Would you like to install them automatically? (y/N): ');
|
|
44
|
+
rl.close();
|
|
45
|
+
return 'y' === answer.toLowerCase() || 'yes' === answer.toLowerCase();
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
async function installPackages(packages, projectRoot)
|
|
@@ -59,8 +50,7 @@ async function installPackages(packages, projectRoot)
|
|
|
59
50
|
try {
|
|
60
51
|
Logger.info('Installing packages: npm install '+packages.join(' '));
|
|
61
52
|
let {execSync} = require('child_process');
|
|
62
|
-
|
|
63
|
-
execSync(installCommand, {stdio: 'inherit', cwd: projectRoot});
|
|
53
|
+
execSync('npm install '+packages.join(' '), {stdio: 'inherit', cwd: projectRoot});
|
|
64
54
|
Logger.info('Dependencies installed successfully.');
|
|
65
55
|
return true;
|
|
66
56
|
} catch(error) {
|
|
@@ -94,42 +84,35 @@ async function createPrismaClientIfNeeded(projectRoot)
|
|
|
94
84
|
}
|
|
95
85
|
let clientPath = FileHandler.joinPaths(projectRoot, 'prisma', 'client');
|
|
96
86
|
if(!FileHandler.exists(clientPath)){
|
|
97
|
-
Logger.critical('Prisma client not found at: '+clientPath);
|
|
98
|
-
Logger.critical('Please run "npx prisma generate" to generate the Prisma client.');
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
let { PrismaClient } = require(clientPath);
|
|
103
|
-
return new PrismaClient();
|
|
104
|
-
} catch(error) {
|
|
105
|
-
Logger.critical('Failed to initialize Prisma client: '+error.message);
|
|
106
87
|
return false;
|
|
107
88
|
}
|
|
89
|
+
return PrismaClientLoader.load(projectRoot, null, null) || false;
|
|
108
90
|
}
|
|
109
91
|
|
|
110
|
-
async function
|
|
92
|
+
async function main()
|
|
111
93
|
{
|
|
94
|
+
if(FileHandler.exists(indexPath)){
|
|
95
|
+
require(indexPath);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
112
98
|
let packageInstallResult = await handlePackageInstallation(projectRoot);
|
|
113
99
|
if(!packageInstallResult){
|
|
114
100
|
process.exit(1);
|
|
115
101
|
}
|
|
116
|
-
|
|
117
|
-
let
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
managerConfig.entitiesTranslations = entitiesModule.entitiesTranslations;
|
|
131
|
-
}
|
|
132
|
-
|
|
102
|
+
let managerConfig = {projectRoot};
|
|
103
|
+
let entitiesPath = FileHandler.joinPaths(
|
|
104
|
+
projectRoot,
|
|
105
|
+
'generated-entities',
|
|
106
|
+
'models',
|
|
107
|
+
'prisma',
|
|
108
|
+
'registered-models-prisma.js'
|
|
109
|
+
);
|
|
110
|
+
if(FileHandler.exists(entitiesPath)){
|
|
111
|
+
let entitiesModule = require(entitiesPath);
|
|
112
|
+
managerConfig.rawRegisteredEntities = entitiesModule.rawRegisteredEntities;
|
|
113
|
+
managerConfig.entitiesConfig = entitiesModule.entitiesConfig;
|
|
114
|
+
managerConfig.entitiesTranslations = entitiesModule.entitiesTranslations;
|
|
115
|
+
}
|
|
133
116
|
let prismaClient = await createPrismaClientIfNeeded(projectRoot);
|
|
134
117
|
if(prismaClient){
|
|
135
118
|
managerConfig.prismaClient = prismaClient;
|
|
@@ -149,7 +132,7 @@ if(FileHandler.exists(entitiesPath)){
|
|
|
149
132
|
});
|
|
150
133
|
}
|
|
151
134
|
|
|
152
|
-
|
|
135
|
+
main().catch((error) => {
|
|
153
136
|
Logger.critical('Failed to handle package installation:', error);
|
|
154
137
|
process.exit(1);
|
|
155
138
|
});
|
|
@@ -491,7 +491,11 @@ class RouterContents
|
|
|
491
491
|
driverResource,
|
|
492
492
|
entityId: id
|
|
493
493
|
});
|
|
494
|
-
|
|
494
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
495
|
+
if(!idProperty){
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
return await entityRepository.loadOneByWithRelations(idProperty, id);
|
|
495
499
|
}
|
|
496
500
|
|
|
497
501
|
async saveEntity(id, entityRepository, entityDataPatch)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Manager Component Validator
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class ManagerComponentValidator
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
static validateProvidedServer(app, appServer)
|
|
13
|
+
{
|
|
14
|
+
if(!app){
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if(!appServer){
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if('function' !== typeof app.use){
|
|
21
|
+
Logger.critical('Invalid app instance provided - missing use method.');
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if('function' !== typeof appServer.listen){
|
|
25
|
+
Logger.critical('Invalid appServer instance provided - missing listen method.');
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static validateProvidedDataServer(dataServer)
|
|
32
|
+
{
|
|
33
|
+
if(!dataServer){
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if('function' !== typeof dataServer.connect){
|
|
37
|
+
Logger.critical('Invalid dataServer instance provided - missing connect method.');
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if('function' !== typeof dataServer.generateEntities){
|
|
41
|
+
Logger.critical('Invalid dataServer instance provided - missing generateEntities method.');
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static validateProvidedAdminManager(adminManager)
|
|
48
|
+
{
|
|
49
|
+
if(!adminManager){
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if('function' !== typeof adminManager.setupAdmin){
|
|
53
|
+
Logger.critical('Invalid adminManager instance provided - missing setupAdmin method.');
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static validateProvidedFrontend(frontend)
|
|
60
|
+
{
|
|
61
|
+
if(!frontend){
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if('function' !== typeof frontend.initialize){
|
|
65
|
+
Logger.critical('Invalid frontend instance provided - missing initialize method.');
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports.ManagerComponentValidator = ManagerComponentValidator;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - Manager Config Loader
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class ManagerConfigLoader
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
static loadFromEnv()
|
|
13
|
+
{
|
|
14
|
+
return {
|
|
15
|
+
host: sc.get(process.env, 'RELDENS_APP_HOST', 'http://localhost'),
|
|
16
|
+
port: Number(sc.get(process.env, 'RELDENS_APP_PORT', 8080)),
|
|
17
|
+
adminPath: sc.get(process.env, 'RELDENS_ADMIN_ROUTE_PATH', '/reldens-admin'),
|
|
18
|
+
adminSecret: sc.get(process.env, 'RELDENS_ADMIN_SECRET', ''),
|
|
19
|
+
database: {
|
|
20
|
+
client: sc.get(process.env, 'RELDENS_DB_CLIENT', 'mysql'),
|
|
21
|
+
host: sc.get(process.env, 'RELDENS_DB_HOST', 'localhost'),
|
|
22
|
+
port: Number(sc.get(process.env, 'RELDENS_DB_PORT', 3306)),
|
|
23
|
+
name: sc.get(process.env, 'RELDENS_DB_NAME', 'reldens_cms'),
|
|
24
|
+
user: sc.get(process.env, 'RELDENS_DB_USER', ''),
|
|
25
|
+
password: sc.get(process.env, 'RELDENS_DB_PASSWORD', ''),
|
|
26
|
+
driver: sc.get(process.env, 'RELDENS_STORAGE_DRIVER', 'prisma')
|
|
27
|
+
},
|
|
28
|
+
publicUrl: sc.get(process.env, 'RELDENS_PUBLIC_URL', '')
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports.ManagerConfigLoader = ManagerConfigLoader;
|