chadstart 1.0.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/.dockerignore +10 -0
- package/.env.example +46 -0
- package/.github/workflows/browser-test.yml +34 -0
- package/.github/workflows/docker-publish.yml +54 -0
- package/.github/workflows/docs.yml +31 -0
- package/.github/workflows/npm-chadstart.yml +27 -0
- package/.github/workflows/npm-sdk.yml +38 -0
- package/.github/workflows/test.yml +85 -0
- package/.weblate +9 -0
- package/Dockerfile +23 -0
- package/README.md +348 -0
- package/admin/index.html +2802 -0
- package/admin/login.html +207 -0
- package/chadstart.example.yml +416 -0
- package/chadstart.schema.json +367 -0
- package/chadstart.yaml +53 -0
- package/cli/cli.js +295 -0
- package/core/api-generator.js +606 -0
- package/core/auth.js +298 -0
- package/core/db.js +384 -0
- package/core/entity-engine.js +166 -0
- package/core/error-reporter.js +132 -0
- package/core/file-storage.js +97 -0
- package/core/functions-engine.js +353 -0
- package/core/openapi.js +171 -0
- package/core/plugin-loader.js +92 -0
- package/core/realtime.js +93 -0
- package/core/schema-validator.js +50 -0
- package/core/seeder.js +231 -0
- package/core/telemetry.js +119 -0
- package/core/upload.js +372 -0
- package/core/workers/php_worker.php +19 -0
- package/core/workers/python_worker.py +33 -0
- package/core/workers/ruby_worker.rb +21 -0
- package/core/yaml-loader.js +64 -0
- package/demo/chadstart.yaml +178 -0
- package/demo/docker-compose.yml +31 -0
- package/demo/functions/greet.go +39 -0
- package/demo/functions/hello.cpp +18 -0
- package/demo/functions/hello.py +13 -0
- package/demo/functions/hello.rb +10 -0
- package/demo/functions/onTodoCreated.js +13 -0
- package/demo/functions/ping.sh +13 -0
- package/demo/functions/stats.js +22 -0
- package/demo/public/index.html +522 -0
- package/docker-compose.yml +17 -0
- package/docs/access-policies.md +155 -0
- package/docs/admin-ui.md +29 -0
- package/docs/angular.md +69 -0
- package/docs/astro.md +71 -0
- package/docs/auth.md +160 -0
- package/docs/cli.md +56 -0
- package/docs/config.md +127 -0
- package/docs/crud.md +627 -0
- package/docs/deploy.md +113 -0
- package/docs/docker.md +59 -0
- package/docs/entities.md +385 -0
- package/docs/functions.md +196 -0
- package/docs/getting-started.md +79 -0
- package/docs/groups.md +85 -0
- package/docs/index.md +5 -0
- package/docs/llm-rules.md +81 -0
- package/docs/middlewares.md +78 -0
- package/docs/overrides/home.html +350 -0
- package/docs/plugins.md +59 -0
- package/docs/react.md +75 -0
- package/docs/realtime.md +43 -0
- package/docs/s3-storage.md +40 -0
- package/docs/security.md +23 -0
- package/docs/stylesheets/extra.css +375 -0
- package/docs/svelte.md +71 -0
- package/docs/telemetry.md +97 -0
- package/docs/upload.md +168 -0
- package/docs/validation.md +115 -0
- package/docs/vue.md +86 -0
- package/docs/webhooks.md +87 -0
- package/index.js +11 -0
- package/locales/en/admin.json +169 -0
- package/mkdocs.yml +82 -0
- package/package.json +65 -0
- package/playwright.config.js +24 -0
- package/public/.gitkeep +0 -0
- package/sdk/README.md +284 -0
- package/sdk/package.json +39 -0
- package/sdk/scripts/build.js +58 -0
- package/sdk/src/index.js +368 -0
- package/sdk/test/sdk.test.cjs +340 -0
- package/sdk/types/index.d.ts +217 -0
- package/server/express-server.js +734 -0
- package/test/access-policies.test.js +96 -0
- package/test/ai.test.js +81 -0
- package/test/api-keys.test.js +361 -0
- package/test/auth.test.js +122 -0
- package/test/browser/admin-ui.spec.js +127 -0
- package/test/browser/global-setup.js +71 -0
- package/test/browser/global-teardown.js +11 -0
- package/test/db.test.js +227 -0
- package/test/entity-engine.test.js +193 -0
- package/test/error-reporter.test.js +140 -0
- package/test/functions-engine.test.js +240 -0
- package/test/groups.test.js +212 -0
- package/test/hot-reload.test.js +153 -0
- package/test/i18n.test.js +173 -0
- package/test/middleware.test.js +76 -0
- package/test/openapi.test.js +67 -0
- package/test/schema-validator.test.js +83 -0
- package/test/sdk.test.js +90 -0
- package/test/seeder.test.js +279 -0
- package/test/settings.test.js +109 -0
- package/test/telemetry.test.js +254 -0
- package/test/test.js +17 -0
- package/test/upload.test.js +265 -0
- package/test/validation.test.js +96 -0
- package/test/yaml-loader.test.js +93 -0
- package/utils/logger.js +24 -0
package/cli/cli.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const command = args[0];
|
|
10
|
+
const DEFAULT_YAML = 'chadstart.yaml';
|
|
11
|
+
|
|
12
|
+
function printUsage() {
|
|
13
|
+
console.log(`
|
|
14
|
+
ChadStart - YAML-first Backend as a Service
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
npx chadstart dev Start server with hot-reload on YAML changes
|
|
18
|
+
npx chadstart start Start server (production mode)
|
|
19
|
+
npx chadstart build Validate YAML config and print schema summary
|
|
20
|
+
npx chadstart seed Seed the database with dummy data
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--config <file> Path to YAML config (default: chadstart.yaml)
|
|
24
|
+
--port <number> Override port from config
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
npx chadstart dev
|
|
28
|
+
npx chadstart dev --config my-backend.yaml
|
|
29
|
+
npx chadstart start --port 8080
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getOption(flag) {
|
|
34
|
+
const idx = args.indexOf(flag);
|
|
35
|
+
return idx !== -1 ? args[idx + 1] : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const yamlPath = path.resolve(getOption('--config') || process.env.CHADSTART_FILE_PATH || DEFAULT_YAML);
|
|
39
|
+
const portOverride = getOption('--port');
|
|
40
|
+
|
|
41
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
42
|
+
printUsage();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (command === 'create') {
|
|
47
|
+
runCreate();
|
|
48
|
+
} else if (command === 'dev') {
|
|
49
|
+
runDev();
|
|
50
|
+
} else if (command === 'start') {
|
|
51
|
+
runStart();
|
|
52
|
+
} else if (command === 'build') {
|
|
53
|
+
runBuild();
|
|
54
|
+
} else if (command === 'seed') {
|
|
55
|
+
runSeed();
|
|
56
|
+
} else {
|
|
57
|
+
console.error(`Unknown command: ${command}`);
|
|
58
|
+
printUsage();
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Commands ────────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async function runCreate() {
|
|
66
|
+
let folderName = process.argv[3];
|
|
67
|
+
if (!folderName) {
|
|
68
|
+
folderName = await askFolderName();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!folderName) {
|
|
72
|
+
console.error('Error: folder name is required.');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const targetDir = path.resolve(process.cwd(), folderName);
|
|
77
|
+
|
|
78
|
+
if (fs.existsSync(targetDir)) {
|
|
79
|
+
console.error(`Error: directory "${folderName}" already exists.`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
84
|
+
|
|
85
|
+
const templateFile = path.join(__dirname, '../demo', 'chadstart.yaml');
|
|
86
|
+
const destFile = path.join(targetDir, 'chadstart.yaml');
|
|
87
|
+
fs.copyFileSync(templateFile, destFile);
|
|
88
|
+
|
|
89
|
+
console.log(`\nCreated project in ${targetDir}`);
|
|
90
|
+
console.log('\nNext steps:');
|
|
91
|
+
console.log(` cd ${folderName}`);
|
|
92
|
+
console.log(' npx chadstart dev\n');
|
|
93
|
+
|
|
94
|
+
async function askFolderName() {
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
const rl = readline.createInterface({
|
|
97
|
+
input: process.stdin,
|
|
98
|
+
output: process.stdout,
|
|
99
|
+
});
|
|
100
|
+
rl.question('Enter the project folder name: ', (answer) => {
|
|
101
|
+
rl.close();
|
|
102
|
+
resolve(answer.trim());
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function runSeed() {
|
|
109
|
+
if (!fs.existsSync(yamlPath)) {
|
|
110
|
+
console.error(`Config not found: ${yamlPath}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const { loadYaml } = require('../core/yaml-loader');
|
|
116
|
+
const { validateSchema } = require('../core/schema-validator');
|
|
117
|
+
const { buildCore } = require('../core/entity-engine');
|
|
118
|
+
const { initDb } = require('../core/db');
|
|
119
|
+
const { seedAll } = require('../core/seeder');
|
|
120
|
+
|
|
121
|
+
const config = loadYaml(yamlPath);
|
|
122
|
+
validateSchema(config);
|
|
123
|
+
const core = buildCore(config);
|
|
124
|
+
initDb(core);
|
|
125
|
+
|
|
126
|
+
console.log('\n🌱 Seeding database...\n');
|
|
127
|
+
const result = await seedAll(core);
|
|
128
|
+
|
|
129
|
+
for (const [name, count] of Object.entries(result.summary)) {
|
|
130
|
+
console.log(` ✅ ${name}: ${count} record${count !== 1 ? 's' : ''} created`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (result.adminEntities.length > 0) {
|
|
134
|
+
console.log('\n🔑 Admin user created:');
|
|
135
|
+
console.log(` Email: ${result.adminEmail}`);
|
|
136
|
+
console.log(` Password: ${result.adminPassword}`);
|
|
137
|
+
console.log(` Entities: ${result.adminEntities.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log('\nDone!\n');
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error(`\n❌ ${err.message}\n`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function runStart() {
|
|
148
|
+
if (!fs.existsSync(yamlPath)) {
|
|
149
|
+
console.error(`Config not found: ${yamlPath}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
applyPortOverride();
|
|
154
|
+
const { startServer } = require('../server/express-server');
|
|
155
|
+
await startServer(yamlPath);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function runDev() {
|
|
159
|
+
if (!fs.existsSync(yamlPath)) {
|
|
160
|
+
console.error(`Config not found: ${yamlPath}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
applyPortOverride();
|
|
165
|
+
|
|
166
|
+
let currentServer = null;
|
|
167
|
+
|
|
168
|
+
async function boot() {
|
|
169
|
+
try {
|
|
170
|
+
if (currentServer) {
|
|
171
|
+
await closeServer(currentServer);
|
|
172
|
+
}
|
|
173
|
+
// Re-require fresh server module on each reload
|
|
174
|
+
clearRequireCache();
|
|
175
|
+
const { startServer } = require('../server/express-server');
|
|
176
|
+
const result = await startServer(yamlPath);
|
|
177
|
+
currentServer = result.server;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error('[dev] Failed to start server:', err.message);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await boot();
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const chokidar = require('chokidar');
|
|
187
|
+
// Watch YAML config
|
|
188
|
+
const watcher = chokidar.watch(yamlPath, { ignoreInitial: true });
|
|
189
|
+
watcher.on('change', async () => {
|
|
190
|
+
console.log(`\n[dev] ${path.basename(yamlPath)} changed — restarting...\n`);
|
|
191
|
+
await boot();
|
|
192
|
+
});
|
|
193
|
+
console.log(`[dev] Watching ${yamlPath} for changes...\n`);
|
|
194
|
+
|
|
195
|
+
// Watch the functions folder for hot reload of function files
|
|
196
|
+
const functionsDir = path.resolve(process.env.CHADSTART_FUNCTIONS_FOLDER || 'functions');
|
|
197
|
+
if (fs.existsSync(functionsDir)) {
|
|
198
|
+
const fnWatcher = chokidar.watch(functionsDir, { ignoreInitial: true, ignorePermissionErrors: true });
|
|
199
|
+
fnWatcher.on('change', (filePath) => {
|
|
200
|
+
console.log(`\n[dev] Function file changed: ${path.relative(process.cwd(), filePath)}`);
|
|
201
|
+
// Only validate and cache-bust JS files; other runtimes (python, bash, etc.) are loaded fresh each invocation
|
|
202
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.mjs')) {
|
|
203
|
+
try { delete require.cache[require.resolve(filePath)]; } catch { /* */ }
|
|
204
|
+
try { require(filePath); console.log(`[dev] ✔ ${path.basename(filePath)} reloaded`); }
|
|
205
|
+
catch (e) { console.error(`[dev] ✖ ${path.basename(filePath)} has errors — restarting server:`, e.message); boot(); }
|
|
206
|
+
} else {
|
|
207
|
+
console.log(`[dev] ✔ ${path.basename(filePath)} updated`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
console.log(`[dev] Watching ${functionsDir} for function file changes...\n`);
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
console.warn('[dev] chokidar not available — hot reload disabled');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function runBuild() {
|
|
218
|
+
if (!fs.existsSync(yamlPath)) {
|
|
219
|
+
console.error(`Config not found: ${yamlPath}`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const { loadYaml } = require('../core/yaml-loader');
|
|
225
|
+
const { validateSchema } = require('../core/schema-validator');
|
|
226
|
+
const { buildCore } = require('../core/entity-engine');
|
|
227
|
+
|
|
228
|
+
const config = loadYaml(yamlPath);
|
|
229
|
+
validateSchema(config);
|
|
230
|
+
const core = buildCore(config);
|
|
231
|
+
|
|
232
|
+
console.log(`\n✅ Config is valid\n`);
|
|
233
|
+
console.log(`Project: ${core.name}`);
|
|
234
|
+
console.log(`Port: ${core.port}`);
|
|
235
|
+
|
|
236
|
+
if (Object.keys(core.authenticableEntities).length > 0) {
|
|
237
|
+
console.log(`\nUser Collections:`);
|
|
238
|
+
for (const uc of Object.values(core.authenticableEntities)) {
|
|
239
|
+
const props = uc.properties.map((p) => `${p.name}:${p.type}`).join(', ') || '(none)';
|
|
240
|
+
const adminFlag = uc.admin ? ' [admin]' : '';
|
|
241
|
+
console.log(` ${uc.name}${adminFlag} [email, password, ${props}]`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log(`\nEntities:`);
|
|
246
|
+
|
|
247
|
+
for (const entity of Object.values(core.entities)) {
|
|
248
|
+
const props = entity.properties.map((p) => `${p.name}:${p.type}`).join(', ');
|
|
249
|
+
const rels = entity.belongsTo.length ? ` | belongsTo: ${entity.belongsTo.join(', ')}` : '';
|
|
250
|
+
console.log(` ${entity.name} [${props}]${rels}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (Object.keys(core.files).length > 0) {
|
|
254
|
+
console.log(`\nFile buckets:`);
|
|
255
|
+
for (const [name, def] of Object.entries(core.files)) {
|
|
256
|
+
console.log(` ${name} -> ${def.path} (public: ${def.public !== false})`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (core.plugins.length > 0) {
|
|
261
|
+
console.log(`\nPlugins:`);
|
|
262
|
+
for (const p of core.plugins) {
|
|
263
|
+
console.log(` ${p.repo || p.path}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
console.log('');
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error(`\n❌ ${err.message}\n`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
function applyPortOverride() {
|
|
277
|
+
if (portOverride) {
|
|
278
|
+
process.env.CHADSTART_PORT = portOverride;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function clearRequireCache() {
|
|
283
|
+
const dir = path.resolve(__dirname, '..');
|
|
284
|
+
for (const key of Object.keys(require.cache)) {
|
|
285
|
+
if (key.startsWith(dir) && !key.includes('node_modules')) {
|
|
286
|
+
delete require.cache[key];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function closeServer(server) {
|
|
292
|
+
return new Promise((resolve) => {
|
|
293
|
+
server.close(resolve);
|
|
294
|
+
});
|
|
295
|
+
}
|