chadstart 1.0.4 → 1.0.6
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/Dockerfile +1 -1
- package/TODO.md +739 -0
- package/admin/index.html +27 -1
- package/{chadstart.example.yml → chadstart.example.yaml} +23 -1
- package/chadstart.schema.json +83 -0
- package/cli/cli.js +124 -35
- package/core/auth.js +160 -3
- package/core/backup.js +191 -0
- package/core/config-loader.js +266 -0
- package/core/db.js +4 -0
- package/core/email.js +170 -0
- package/core/entity-engine.js +4 -0
- package/core/logs.js +179 -0
- package/core/migrations.js +23 -18
- package/core/openapi.js +6 -2
- package/core/yaml-loader.js +8 -53
- package/docs/llm-rules.md +1 -1
- package/package.json +3 -1
- package/server/express-server.js +149 -18
- package/test/backup.test.js +146 -0
- package/test/config-loader.test.js +257 -0
- package/test/email.test.js +362 -0
- package/test/logs.test.js +239 -0
- package/test/verification.test.js +439 -0
package/admin/index.html
CHANGED
|
@@ -261,6 +261,7 @@
|
|
|
261
261
|
<button onclick="showConfigTab('files')" id="cfg-tab-files" class="cfg-tab px-3 py-1.5 text-xs rounded border border-cs-border text-cs-muted hover:text-cs-text" style="transition:color 150ms,background 150ms;" data-i18n="config.tabs.files">Files & Uploads</button>
|
|
262
262
|
<button onclick="showConfigTab('settings')" id="cfg-tab-settings" class="cfg-tab px-3 py-1.5 text-xs rounded border border-cs-border text-cs-muted hover:text-cs-text" style="transition:color 150ms,background 150ms;" data-i18n="config.tabs.settings">Settings</button>
|
|
263
263
|
<button onclick="showConfigTab('all')" id="cfg-tab-all" class="cfg-tab px-3 py-1.5 text-xs rounded border border-cs-border text-cs-muted hover:text-cs-text" style="transition:color 150ms,background 150ms;" data-i18n="config.tabs.all">All</button>
|
|
264
|
+
<span id="cfg-format-badge" class="ml-auto px-2 py-1 text-xs rounded font-mono" style="background:var(--color-cs-border);color:var(--color-cs-muted);" title="Config format">YAML</span>
|
|
264
265
|
</div>
|
|
265
266
|
<p id="cfg-section-desc" class="text-xs mb-3" style="color:var(--color-cs-muted);"></p>
|
|
266
267
|
<div id="cfg-form" class="hidden mb-6"></div>
|
|
@@ -674,6 +675,8 @@
|
|
|
674
675
|
// Config editor
|
|
675
676
|
let configMode = false;
|
|
676
677
|
let configData = null;
|
|
678
|
+
let configFormat = 'yaml';
|
|
679
|
+
let configWritable = true;
|
|
677
680
|
let configActiveTab = 'general';
|
|
678
681
|
let _cfgEntityId = 0, _cfgEndpointId = 0, _cfgFileId = 0, _cfgRlId = 0, _cfgTriggerId = 0;
|
|
679
682
|
|
|
@@ -1706,13 +1709,36 @@ const _LBL = 'block text-xs mb-1';
|
|
|
1706
1709
|
|
|
1707
1710
|
async function loadConfig() {
|
|
1708
1711
|
try {
|
|
1709
|
-
const r = await
|
|
1712
|
+
const [r, infoRes] = await Promise.all([
|
|
1713
|
+
apiFetch('/admin/config'),
|
|
1714
|
+
apiFetch('/admin/config-info'),
|
|
1715
|
+
]);
|
|
1710
1716
|
if (!r.ok) throw new Error('Failed to load config');
|
|
1711
1717
|
configData = await r.json();
|
|
1718
|
+
if (infoRes.ok) {
|
|
1719
|
+
const info = await infoRes.json();
|
|
1720
|
+
configFormat = info.format || 'yaml';
|
|
1721
|
+
configWritable = info.writable !== false;
|
|
1722
|
+
}
|
|
1723
|
+
updateConfigFormatBadge();
|
|
1712
1724
|
showConfigTab('general');
|
|
1713
1725
|
} catch (e) { toast(e.message, 'error'); }
|
|
1714
1726
|
}
|
|
1715
1727
|
|
|
1728
|
+
function updateConfigFormatBadge() {
|
|
1729
|
+
const badge = document.getElementById('cfg-format-badge');
|
|
1730
|
+
if (!badge) return;
|
|
1731
|
+
badge.textContent = configFormat.toUpperCase();
|
|
1732
|
+
badge.title = configWritable
|
|
1733
|
+
? 'Config format: ' + configFormat
|
|
1734
|
+
: 'Config format: ' + configFormat + ' (read-only)';
|
|
1735
|
+
const saveBtn = document.getElementById('cfg-save-btn');
|
|
1736
|
+
if (saveBtn) {
|
|
1737
|
+
saveBtn.disabled = !configWritable;
|
|
1738
|
+
saveBtn.title = configWritable ? '' : 'Saving is not supported for ' + configFormat + ' configs. Convert to YAML, JSON, or JSON5 to enable saving.';
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1716
1742
|
function showConfigTab(tab) {
|
|
1717
1743
|
configActiveTab = tab;
|
|
1718
1744
|
document.querySelectorAll('.cfg-tab').forEach(b => b.classList.remove('active'));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# chadstart.example.
|
|
1
|
+
# chadstart.example.yaml
|
|
2
2
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
3
3
|
# Reference file that lists every available option in a ChadStart YAML config.
|
|
4
4
|
# Copy this file to chadstart.yaml and remove / adjust the sections you need.
|
|
@@ -415,6 +415,28 @@ sentry:
|
|
|
415
415
|
tracesSampleRate: 1.0 # Fraction of transactions to sample (0.0–1.0)
|
|
416
416
|
debug: false # Enable Sentry SDK debug logging
|
|
417
417
|
|
|
418
|
+
# ── Email / SMTP ──────────────────────────────────────────────────────────────
|
|
419
|
+
# Configure SMTP for transactional emails (verification, password reset, etc.).
|
|
420
|
+
# The SMTP password is a secret and must be provided via the SMTP_PASS env var.
|
|
421
|
+
# All fields can be overridden by environment variables:
|
|
422
|
+
# SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM
|
|
423
|
+
|
|
424
|
+
email:
|
|
425
|
+
host: smtp.example.com # SMTP server hostname. Overridable via SMTP_HOST
|
|
426
|
+
port: 587 # SMTP port (587=STARTTLS, 465=SSL, 25=plain). Overridable via SMTP_PORT
|
|
427
|
+
username: noreply@example.com # SMTP login. Overridable via SMTP_USER
|
|
428
|
+
from: "My App <noreply@example.com>" # Default sender. Overridable via SMTP_FROM
|
|
429
|
+
secure: false # Use TLS (auto-detected from port if omitted)
|
|
430
|
+
templates:
|
|
431
|
+
verification:
|
|
432
|
+
subject: "Verify your email for {{appName}}"
|
|
433
|
+
text: "Hi {{name}},\n\nPlease verify your email by visiting:\n{{link}}\n\nThanks,\n{{appName}}"
|
|
434
|
+
html: "<h2>Verify your email</h2><p>Hi {{name}},</p><p>Please verify your email by clicking the link below:</p><p><a href=\"{{link}}\">Verify Email</a></p><p>Thanks,<br>{{appName}}</p>"
|
|
435
|
+
passwordReset:
|
|
436
|
+
subject: "Reset your password for {{appName}}"
|
|
437
|
+
text: "Hi {{name}},\n\nYou requested a password reset. Visit:\n{{link}}\n\nIf you didn't request this, ignore this email.\n\nThanks,\n{{appName}}"
|
|
438
|
+
html: "<h2>Reset your password</h2><p>Hi {{name}},</p><p>You requested a password reset. Click the link below:</p><p><a href=\"{{link}}\">Reset Password</a></p><p>If you didn't request this, ignore this email.</p><p>Thanks,<br>{{appName}}</p>"
|
|
439
|
+
|
|
418
440
|
# ── OAuth / Social Login ─────────────────────────────────────────────────────
|
|
419
441
|
# Powered by the "grant" library — supports 200+ OAuth providers.
|
|
420
442
|
# Secrets (client keys / secrets) MUST be set via environment variables:
|
package/chadstart.schema.json
CHANGED
|
@@ -125,6 +125,78 @@
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
},
|
|
128
|
+
"logs": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"description": "Request logging configuration. Logs are stored in a _cs_logs system table and accessible via GET /admin/logs.",
|
|
131
|
+
"additionalProperties": false,
|
|
132
|
+
"properties": {
|
|
133
|
+
"retention": {
|
|
134
|
+
"type": "integer",
|
|
135
|
+
"default": 30,
|
|
136
|
+
"description": "Number of days to keep log entries. 0 = keep forever. Default: 30."
|
|
137
|
+
},
|
|
138
|
+
"exclude": {
|
|
139
|
+
"type": "array",
|
|
140
|
+
"items": { "type": "string" },
|
|
141
|
+
"description": "URL path prefixes to exclude from logging (e.g. ['/health', '/admin/vendor'])."
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"backup": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"description": "Database backup configuration. Backups are created via POST /admin/backup or `npx chadstart backup`.",
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"properties": {
|
|
150
|
+
"dir": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"default": "backups",
|
|
153
|
+
"description": "Directory to store backup files. Overridable via BACKUP_DIR env var. Default: backups."
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"email": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"description": "Email / SMTP configuration for sending transactional emails (verification, password reset, notifications). The SMTP password must be supplied via the SMTP_PASS environment variable — never put it here. All fields can be overridden by SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_FROM env vars.",
|
|
160
|
+
"additionalProperties": false,
|
|
161
|
+
"properties": {
|
|
162
|
+
"host": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"description": "SMTP server hostname (e.g. smtp.gmail.com). Overridable via SMTP_HOST env var."
|
|
165
|
+
},
|
|
166
|
+
"port": {
|
|
167
|
+
"type": "integer",
|
|
168
|
+
"default": 587,
|
|
169
|
+
"description": "SMTP server port. Common values: 587 (STARTTLS), 465 (SSL/TLS), 25 (unencrypted). Overridable via SMTP_PORT env var."
|
|
170
|
+
},
|
|
171
|
+
"username": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"description": "SMTP login username. Overridable via SMTP_USER env var."
|
|
174
|
+
},
|
|
175
|
+
"from": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"description": "Default sender address (e.g. 'My App <noreply@example.com>'). Overridable via SMTP_FROM env var."
|
|
178
|
+
},
|
|
179
|
+
"secure": {
|
|
180
|
+
"type": "boolean",
|
|
181
|
+
"description": "Use TLS when connecting to the server. Defaults to true for port 465, false otherwise."
|
|
182
|
+
},
|
|
183
|
+
"templates": {
|
|
184
|
+
"type": "object",
|
|
185
|
+
"description": "Email templates with {{variable}} placeholders. Used by built-in flows (verification, password reset).",
|
|
186
|
+
"additionalProperties": false,
|
|
187
|
+
"properties": {
|
|
188
|
+
"verification": {
|
|
189
|
+
"$ref": "#/$defs/emailTemplate",
|
|
190
|
+
"description": "Template for email verification messages."
|
|
191
|
+
},
|
|
192
|
+
"passwordReset": {
|
|
193
|
+
"$ref": "#/$defs/emailTemplate",
|
|
194
|
+
"description": "Template for password reset messages."
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
128
200
|
"oauth": {
|
|
129
201
|
"type": "object",
|
|
130
202
|
"description": "OAuth / social login configuration powered by the grant library. Secrets (client keys and secrets) must be supplied via OAUTH_<PROVIDER>_KEY and OAUTH_<PROVIDER>_SECRET environment variables.",
|
|
@@ -292,6 +364,7 @@
|
|
|
292
364
|
"type": "object",
|
|
293
365
|
"properties": {
|
|
294
366
|
"authenticable": { "type": "boolean", "default": false, "description": "Makes this entity authenticable (adds email + password fields, enables login/signup)." },
|
|
367
|
+
"requireEmailVerification": { "type": "boolean", "default": false, "description": "When true, login is blocked until the user verifies their email address." },
|
|
295
368
|
"single": { "type": "boolean", "default": false, "description": "Single entity — only one record exists (no create/delete)." },
|
|
296
369
|
"mainProp": { "type": "string", "description": "Identifier property used in the admin panel." },
|
|
297
370
|
"nameSingular": { "type": "string" },
|
|
@@ -400,6 +473,16 @@
|
|
|
400
473
|
"ttl": { "type": "integer", "description": "Time window in milliseconds." }
|
|
401
474
|
}
|
|
402
475
|
},
|
|
476
|
+
"emailTemplate": {
|
|
477
|
+
"type": "object",
|
|
478
|
+
"description": "An email template with subject, text body, and optional HTML body. Supports {{variable}} placeholders.",
|
|
479
|
+
"additionalProperties": false,
|
|
480
|
+
"properties": {
|
|
481
|
+
"subject": { "type": "string", "description": "Email subject line. Supports {{appName}}, {{name}}, {{link}} placeholders." },
|
|
482
|
+
"text": { "type": "string", "description": "Plain-text email body. Supports {{appName}}, {{name}}, {{link}} placeholders." },
|
|
483
|
+
"html": { "type": "string", "description": "HTML email body. Supports {{appName}}, {{name}}, {{link}} placeholders." }
|
|
484
|
+
}
|
|
485
|
+
},
|
|
403
486
|
"oauthProvider": {
|
|
404
487
|
"type": "object",
|
|
405
488
|
"description": "Configuration for a single OAuth provider. The key and secret should be set via OAUTH_<PROVIDER>_KEY and OAUTH_<PROVIDER>_SECRET environment variables.",
|
package/cli/cli.js
CHANGED
|
@@ -7,23 +7,26 @@ const fs = require('fs');
|
|
|
7
7
|
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
const command = args[0];
|
|
10
|
-
const
|
|
10
|
+
const DEFAULT_CONFIG = 'chadstart.yaml';
|
|
11
11
|
|
|
12
12
|
function printUsage() {
|
|
13
13
|
console.log(`
|
|
14
|
-
ChadStart -
|
|
14
|
+
ChadStart - Config-driven Backend as a Service
|
|
15
15
|
|
|
16
16
|
Usage:
|
|
17
|
-
npx chadstart dev Start server with hot-reload on
|
|
17
|
+
npx chadstart dev Start server with hot-reload on config changes
|
|
18
18
|
npx chadstart start Start server (production mode)
|
|
19
|
-
npx chadstart build Validate
|
|
19
|
+
npx chadstart build Validate config and print schema summary
|
|
20
20
|
npx chadstart seed Seed the database with dummy data
|
|
21
21
|
npx chadstart migrate Run pending database migrations
|
|
22
|
-
npx chadstart migrate:generate Generate migration from
|
|
22
|
+
npx chadstart migrate:generate Generate migration from config diff (git-based)
|
|
23
23
|
npx chadstart migrate:status Show current migration status
|
|
24
|
+
npx chadstart backup Create a database backup
|
|
25
|
+
npx chadstart restore <file> Restore database from a backup file
|
|
24
26
|
|
|
25
27
|
Options:
|
|
26
|
-
--config <file> Path to
|
|
28
|
+
--config <file> Path to config file (default: auto-discover)
|
|
29
|
+
Supported formats: yaml, json, json5, jsonnet, js
|
|
27
30
|
--port <number> Override port from config
|
|
28
31
|
--migrations-dir <dir> Path to migrations directory (default: migrations)
|
|
29
32
|
--description <text> Description for generated migration
|
|
@@ -31,6 +34,7 @@ Options:
|
|
|
31
34
|
Examples:
|
|
32
35
|
npx chadstart dev
|
|
33
36
|
npx chadstart dev --config my-backend.yaml
|
|
37
|
+
npx chadstart dev --config chadstart.json
|
|
34
38
|
npx chadstart start --port 8080
|
|
35
39
|
npx chadstart migrate:generate --description add-posts-table
|
|
36
40
|
npx chadstart migrate
|
|
@@ -42,7 +46,16 @@ function getOption(flag) {
|
|
|
42
46
|
return idx !== -1 ? args[idx + 1] : null;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
function resolveConfigPath() {
|
|
50
|
+
const explicit = getOption('--config') || process.env.CHADSTART_FILE_PATH;
|
|
51
|
+
if (explicit) return path.resolve(explicit);
|
|
52
|
+
// Auto-discover: try each supported filename in priority order
|
|
53
|
+
const { discoverConfigFile } = require('../core/config-loader');
|
|
54
|
+
const found = discoverConfigFile(process.cwd());
|
|
55
|
+
return found || path.resolve(DEFAULT_CONFIG); // fall back to default for error messages
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const configPath = resolveConfigPath();
|
|
46
59
|
const portOverride = getOption('--port');
|
|
47
60
|
|
|
48
61
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
@@ -66,6 +79,10 @@ if (command === 'create') {
|
|
|
66
79
|
runMigrateGenerate();
|
|
67
80
|
} else if (command === 'migrate:status') {
|
|
68
81
|
runMigrateStatus();
|
|
82
|
+
} else if (command === 'backup') {
|
|
83
|
+
runBackup();
|
|
84
|
+
} else if (command === 'restore') {
|
|
85
|
+
runRestore();
|
|
69
86
|
} else {
|
|
70
87
|
console.error(`Unknown command: ${command}`);
|
|
71
88
|
printUsage();
|
|
@@ -119,19 +136,19 @@ async function runCreate() {
|
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
async function runSeed() {
|
|
122
|
-
if (!fs.existsSync(
|
|
123
|
-
console.error(`Config not found: ${
|
|
139
|
+
if (!fs.existsSync(configPath)) {
|
|
140
|
+
console.error(`Config not found: ${configPath}`);
|
|
124
141
|
process.exit(1);
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
try {
|
|
128
|
-
const {
|
|
145
|
+
const { loadConfig } = require('../core/config-loader');
|
|
129
146
|
const { validateSchema } = require('../core/schema-validator');
|
|
130
147
|
const { buildCore } = require('../core/entity-engine');
|
|
131
148
|
const { initDb } = require('../core/db');
|
|
132
149
|
const { seedAll } = require('../core/seeder');
|
|
133
150
|
|
|
134
|
-
const config =
|
|
151
|
+
const config = loadConfig(configPath);
|
|
135
152
|
validateSchema(config);
|
|
136
153
|
const core = buildCore(config);
|
|
137
154
|
initDb(core);
|
|
@@ -158,19 +175,19 @@ async function runSeed() {
|
|
|
158
175
|
}
|
|
159
176
|
|
|
160
177
|
async function runStart() {
|
|
161
|
-
if (!fs.existsSync(
|
|
162
|
-
console.error(`Config not found: ${
|
|
178
|
+
if (!fs.existsSync(configPath)) {
|
|
179
|
+
console.error(`Config not found: ${configPath}`);
|
|
163
180
|
process.exit(1);
|
|
164
181
|
}
|
|
165
182
|
|
|
166
183
|
applyPortOverride();
|
|
167
184
|
const { startServer } = require('../server/express-server');
|
|
168
|
-
await startServer(
|
|
185
|
+
await startServer(configPath);
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
async function runDev() {
|
|
172
|
-
if (!fs.existsSync(
|
|
173
|
-
console.error(`Config not found: ${
|
|
189
|
+
if (!fs.existsSync(configPath)) {
|
|
190
|
+
console.error(`Config not found: ${configPath}`);
|
|
174
191
|
process.exit(1);
|
|
175
192
|
}
|
|
176
193
|
|
|
@@ -186,7 +203,7 @@ async function runDev() {
|
|
|
186
203
|
// Re-require fresh server module on each reload
|
|
187
204
|
clearRequireCache();
|
|
188
205
|
const { startServer } = require('../server/express-server');
|
|
189
|
-
const result = await startServer(
|
|
206
|
+
const result = await startServer(configPath);
|
|
190
207
|
currentServer = result.server;
|
|
191
208
|
} catch (err) {
|
|
192
209
|
console.error('[dev] Failed to start server:', err.message);
|
|
@@ -198,12 +215,12 @@ async function runDev() {
|
|
|
198
215
|
try {
|
|
199
216
|
const chokidar = require('chokidar');
|
|
200
217
|
// Watch YAML config
|
|
201
|
-
const watcher = chokidar.watch(
|
|
218
|
+
const watcher = chokidar.watch(configPath, { ignoreInitial: true });
|
|
202
219
|
watcher.on('change', async () => {
|
|
203
|
-
console.log(`\n[dev] ${path.basename(
|
|
220
|
+
console.log(`\n[dev] ${path.basename(configPath)} changed — restarting...\n`);
|
|
204
221
|
await boot();
|
|
205
222
|
});
|
|
206
|
-
console.log(`[dev] Watching ${
|
|
223
|
+
console.log(`[dev] Watching ${configPath} for changes...\n`);
|
|
207
224
|
|
|
208
225
|
// Watch the functions folder for hot reload of function files
|
|
209
226
|
const functionsDir = path.resolve(process.env.CHADSTART_FUNCTIONS_FOLDER || 'functions');
|
|
@@ -228,17 +245,17 @@ async function runDev() {
|
|
|
228
245
|
}
|
|
229
246
|
|
|
230
247
|
function runBuild() {
|
|
231
|
-
if (!fs.existsSync(
|
|
232
|
-
console.error(`Config not found: ${
|
|
248
|
+
if (!fs.existsSync(configPath)) {
|
|
249
|
+
console.error(`Config not found: ${configPath}`);
|
|
233
250
|
process.exit(1);
|
|
234
251
|
}
|
|
235
252
|
|
|
236
253
|
try {
|
|
237
|
-
const {
|
|
254
|
+
const { loadConfig } = require('../core/config-loader');
|
|
238
255
|
const { validateSchema } = require('../core/schema-validator');
|
|
239
256
|
const { buildCore } = require('../core/entity-engine');
|
|
240
257
|
|
|
241
|
-
const config =
|
|
258
|
+
const config = loadConfig(configPath);
|
|
242
259
|
validateSchema(config);
|
|
243
260
|
const core = buildCore(config);
|
|
244
261
|
|
|
@@ -290,20 +307,20 @@ const migrationsDir = path.resolve(getOption('--migrations-dir') || 'migrations'
|
|
|
290
307
|
const migrationDescription = getOption('--description') || null;
|
|
291
308
|
|
|
292
309
|
async function runMigrate() {
|
|
293
|
-
if (!fs.existsSync(
|
|
294
|
-
console.error(`Config not found: ${
|
|
310
|
+
if (!fs.existsSync(configPath)) {
|
|
311
|
+
console.error(`Config not found: ${configPath}`);
|
|
295
312
|
process.exit(1);
|
|
296
313
|
}
|
|
297
314
|
|
|
298
315
|
try {
|
|
299
|
-
const {
|
|
316
|
+
const { loadConfig } = require('../core/config-loader');
|
|
300
317
|
const { validateSchema } = require('../core/schema-validator');
|
|
301
318
|
const { buildCore } = require('../core/entity-engine');
|
|
302
319
|
const { initDb, closeDb } = require('../core/db');
|
|
303
320
|
const { runMigrations, buildExecQueryFn } = require('../core/migrations');
|
|
304
321
|
const dbModule = require('../core/db');
|
|
305
322
|
|
|
306
|
-
const config =
|
|
323
|
+
const config = loadConfig(configPath);
|
|
307
324
|
validateSchema(config);
|
|
308
325
|
const core = buildCore(config);
|
|
309
326
|
await initDb(core);
|
|
@@ -331,8 +348,8 @@ async function runMigrate() {
|
|
|
331
348
|
}
|
|
332
349
|
|
|
333
350
|
async function runMigrateGenerate() {
|
|
334
|
-
if (!fs.existsSync(
|
|
335
|
-
console.error(`Config not found: ${
|
|
351
|
+
if (!fs.existsSync(configPath)) {
|
|
352
|
+
console.error(`Config not found: ${configPath}`);
|
|
336
353
|
process.exit(1);
|
|
337
354
|
}
|
|
338
355
|
|
|
@@ -341,7 +358,7 @@ async function runMigrateGenerate() {
|
|
|
341
358
|
|
|
342
359
|
console.log('\n📝 Generating migration from YAML diff...\n');
|
|
343
360
|
|
|
344
|
-
const result = generateMigration(
|
|
361
|
+
const result = generateMigration(configPath, migrationsDir, migrationDescription);
|
|
345
362
|
|
|
346
363
|
if (result.isEmpty) {
|
|
347
364
|
console.log(' ℹ️ No schema changes detected — nothing to generate.\n');
|
|
@@ -358,20 +375,20 @@ async function runMigrateGenerate() {
|
|
|
358
375
|
}
|
|
359
376
|
|
|
360
377
|
async function runMigrateStatus() {
|
|
361
|
-
if (!fs.existsSync(
|
|
362
|
-
console.error(`Config not found: ${
|
|
378
|
+
if (!fs.existsSync(configPath)) {
|
|
379
|
+
console.error(`Config not found: ${configPath}`);
|
|
363
380
|
process.exit(1);
|
|
364
381
|
}
|
|
365
382
|
|
|
366
383
|
try {
|
|
367
|
-
const {
|
|
384
|
+
const { loadConfig } = require('../core/config-loader');
|
|
368
385
|
const { validateSchema } = require('../core/schema-validator');
|
|
369
386
|
const { buildCore } = require('../core/entity-engine');
|
|
370
387
|
const { initDb, closeDb } = require('../core/db');
|
|
371
388
|
const { getMigrationStatus, buildExecQueryFn } = require('../core/migrations');
|
|
372
389
|
const dbModule = require('../core/db');
|
|
373
390
|
|
|
374
|
-
const config =
|
|
391
|
+
const config = loadConfig(configPath);
|
|
375
392
|
validateSchema(config);
|
|
376
393
|
const core = buildCore(config);
|
|
377
394
|
await initDb(core);
|
|
@@ -402,6 +419,78 @@ async function runMigrateStatus() {
|
|
|
402
419
|
|
|
403
420
|
// ─── Other helpers ───────────────────────────────────────────────────────────
|
|
404
421
|
|
|
422
|
+
async function runBackup() {
|
|
423
|
+
if (!fs.existsSync(configPath)) {
|
|
424
|
+
console.error(`Config not found: ${configPath}`);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
const { loadConfig } = require('../core/config-loader');
|
|
430
|
+
const { validateSchema } = require('../core/schema-validator');
|
|
431
|
+
const { buildCore } = require('../core/entity-engine');
|
|
432
|
+
const { initDb, closeDb } = require('../core/db');
|
|
433
|
+
const { createBackup } = require('../core/backup');
|
|
434
|
+
|
|
435
|
+
const config = loadConfig(configPath);
|
|
436
|
+
validateSchema(config);
|
|
437
|
+
const core = buildCore(config);
|
|
438
|
+
await initDb(core);
|
|
439
|
+
|
|
440
|
+
console.log('\n💾 Creating backup...\n');
|
|
441
|
+
const result = await createBackup(core.backup);
|
|
442
|
+
console.log(` ✅ Backup created: ${result.file}`);
|
|
443
|
+
console.log(` Path: ${result.path}`);
|
|
444
|
+
console.log(` Size: ${(result.size / 1024).toFixed(1)} KB`);
|
|
445
|
+
console.log(` Engine: ${result.engine}\n`);
|
|
446
|
+
|
|
447
|
+
await closeDb();
|
|
448
|
+
} catch (err) {
|
|
449
|
+
console.error(`\n❌ ${err.message}\n`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function runRestore() {
|
|
455
|
+
const backupFile = args[1];
|
|
456
|
+
if (!backupFile) {
|
|
457
|
+
console.error('Error: backup file name is required. Usage: npx chadstart restore <file>');
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!fs.existsSync(configPath)) {
|
|
462
|
+
console.error(`Config not found: ${configPath}`);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
try {
|
|
467
|
+
const { loadConfig } = require('../core/config-loader');
|
|
468
|
+
const { validateSchema } = require('../core/schema-validator');
|
|
469
|
+
const { buildCore } = require('../core/entity-engine');
|
|
470
|
+
const { initDb } = require('../core/db');
|
|
471
|
+
const { restoreBackup } = require('../core/backup');
|
|
472
|
+
|
|
473
|
+
const config = loadConfig(configPath);
|
|
474
|
+
validateSchema(config);
|
|
475
|
+
const core = buildCore(config);
|
|
476
|
+
await initDb(core);
|
|
477
|
+
|
|
478
|
+
console.log(`\n🔄 Restoring from ${backupFile}...\n`);
|
|
479
|
+
const result = await restoreBackup(backupFile, core.backup);
|
|
480
|
+
if (result.success) {
|
|
481
|
+
console.log(` ✅ ${result.message}\n`);
|
|
482
|
+
} else {
|
|
483
|
+
console.error(` ❌ ${result.message}\n`);
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
} catch (err) {
|
|
487
|
+
console.error(`\n❌ ${err.message}\n`);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ─── Other helpers ───────────────────────────────────────────────────────────
|
|
493
|
+
|
|
405
494
|
function applyPortOverride() {
|
|
406
495
|
if (portOverride) {
|
|
407
496
|
process.env.CHADSTART_PORT = portOverride;
|