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.
Files changed (115) hide show
  1. package/.dockerignore +10 -0
  2. package/.env.example +46 -0
  3. package/.github/workflows/browser-test.yml +34 -0
  4. package/.github/workflows/docker-publish.yml +54 -0
  5. package/.github/workflows/docs.yml +31 -0
  6. package/.github/workflows/npm-chadstart.yml +27 -0
  7. package/.github/workflows/npm-sdk.yml +38 -0
  8. package/.github/workflows/test.yml +85 -0
  9. package/.weblate +9 -0
  10. package/Dockerfile +23 -0
  11. package/README.md +348 -0
  12. package/admin/index.html +2802 -0
  13. package/admin/login.html +207 -0
  14. package/chadstart.example.yml +416 -0
  15. package/chadstart.schema.json +367 -0
  16. package/chadstart.yaml +53 -0
  17. package/cli/cli.js +295 -0
  18. package/core/api-generator.js +606 -0
  19. package/core/auth.js +298 -0
  20. package/core/db.js +384 -0
  21. package/core/entity-engine.js +166 -0
  22. package/core/error-reporter.js +132 -0
  23. package/core/file-storage.js +97 -0
  24. package/core/functions-engine.js +353 -0
  25. package/core/openapi.js +171 -0
  26. package/core/plugin-loader.js +92 -0
  27. package/core/realtime.js +93 -0
  28. package/core/schema-validator.js +50 -0
  29. package/core/seeder.js +231 -0
  30. package/core/telemetry.js +119 -0
  31. package/core/upload.js +372 -0
  32. package/core/workers/php_worker.php +19 -0
  33. package/core/workers/python_worker.py +33 -0
  34. package/core/workers/ruby_worker.rb +21 -0
  35. package/core/yaml-loader.js +64 -0
  36. package/demo/chadstart.yaml +178 -0
  37. package/demo/docker-compose.yml +31 -0
  38. package/demo/functions/greet.go +39 -0
  39. package/demo/functions/hello.cpp +18 -0
  40. package/demo/functions/hello.py +13 -0
  41. package/demo/functions/hello.rb +10 -0
  42. package/demo/functions/onTodoCreated.js +13 -0
  43. package/demo/functions/ping.sh +13 -0
  44. package/demo/functions/stats.js +22 -0
  45. package/demo/public/index.html +522 -0
  46. package/docker-compose.yml +17 -0
  47. package/docs/access-policies.md +155 -0
  48. package/docs/admin-ui.md +29 -0
  49. package/docs/angular.md +69 -0
  50. package/docs/astro.md +71 -0
  51. package/docs/auth.md +160 -0
  52. package/docs/cli.md +56 -0
  53. package/docs/config.md +127 -0
  54. package/docs/crud.md +627 -0
  55. package/docs/deploy.md +113 -0
  56. package/docs/docker.md +59 -0
  57. package/docs/entities.md +385 -0
  58. package/docs/functions.md +196 -0
  59. package/docs/getting-started.md +79 -0
  60. package/docs/groups.md +85 -0
  61. package/docs/index.md +5 -0
  62. package/docs/llm-rules.md +81 -0
  63. package/docs/middlewares.md +78 -0
  64. package/docs/overrides/home.html +350 -0
  65. package/docs/plugins.md +59 -0
  66. package/docs/react.md +75 -0
  67. package/docs/realtime.md +43 -0
  68. package/docs/s3-storage.md +40 -0
  69. package/docs/security.md +23 -0
  70. package/docs/stylesheets/extra.css +375 -0
  71. package/docs/svelte.md +71 -0
  72. package/docs/telemetry.md +97 -0
  73. package/docs/upload.md +168 -0
  74. package/docs/validation.md +115 -0
  75. package/docs/vue.md +86 -0
  76. package/docs/webhooks.md +87 -0
  77. package/index.js +11 -0
  78. package/locales/en/admin.json +169 -0
  79. package/mkdocs.yml +82 -0
  80. package/package.json +65 -0
  81. package/playwright.config.js +24 -0
  82. package/public/.gitkeep +0 -0
  83. package/sdk/README.md +284 -0
  84. package/sdk/package.json +39 -0
  85. package/sdk/scripts/build.js +58 -0
  86. package/sdk/src/index.js +368 -0
  87. package/sdk/test/sdk.test.cjs +340 -0
  88. package/sdk/types/index.d.ts +217 -0
  89. package/server/express-server.js +734 -0
  90. package/test/access-policies.test.js +96 -0
  91. package/test/ai.test.js +81 -0
  92. package/test/api-keys.test.js +361 -0
  93. package/test/auth.test.js +122 -0
  94. package/test/browser/admin-ui.spec.js +127 -0
  95. package/test/browser/global-setup.js +71 -0
  96. package/test/browser/global-teardown.js +11 -0
  97. package/test/db.test.js +227 -0
  98. package/test/entity-engine.test.js +193 -0
  99. package/test/error-reporter.test.js +140 -0
  100. package/test/functions-engine.test.js +240 -0
  101. package/test/groups.test.js +212 -0
  102. package/test/hot-reload.test.js +153 -0
  103. package/test/i18n.test.js +173 -0
  104. package/test/middleware.test.js +76 -0
  105. package/test/openapi.test.js +67 -0
  106. package/test/schema-validator.test.js +83 -0
  107. package/test/sdk.test.js +90 -0
  108. package/test/seeder.test.js +279 -0
  109. package/test/settings.test.js +109 -0
  110. package/test/telemetry.test.js +254 -0
  111. package/test/test.js +17 -0
  112. package/test/upload.test.js +265 -0
  113. package/test/validation.test.js +96 -0
  114. package/test/yaml-loader.test.js +93 -0
  115. 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
+ }