@donkeylabs/cli 2.0.15 → 2.0.17

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 (85) hide show
  1. package/package.json +1 -1
  2. package/src/commands/config.ts +610 -0
  3. package/src/commands/deploy-enhanced.ts +354 -0
  4. package/src/commands/deploy.ts +204 -0
  5. package/src/commands/docs.ts +207 -0
  6. package/src/commands/init-enhanced.ts +1994 -0
  7. package/src/deployment/manager.ts +356 -0
  8. package/src/index.ts +58 -19
  9. package/templates/starter/.env.example +0 -44
  10. package/templates/starter/.gitignore.template +0 -4
  11. package/templates/starter/donkeylabs.config.ts +0 -6
  12. package/templates/starter/package.json +0 -21
  13. package/templates/starter/src/index.ts +0 -54
  14. package/templates/starter/src/plugins/stats/index.ts +0 -105
  15. package/templates/starter/src/routes/health/handlers/ping.ts +0 -22
  16. package/templates/starter/src/routes/health/index.ts +0 -19
  17. package/templates/starter/tsconfig.json +0 -27
  18. package/templates/sveltekit-app/.env.example +0 -59
  19. package/templates/sveltekit-app/README.md +0 -103
  20. package/templates/sveltekit-app/bun.lock +0 -683
  21. package/templates/sveltekit-app/donkeylabs.config.ts +0 -12
  22. package/templates/sveltekit-app/package.json +0 -38
  23. package/templates/sveltekit-app/src/app.css +0 -40
  24. package/templates/sveltekit-app/src/app.html +0 -12
  25. package/templates/sveltekit-app/src/hooks.server.ts +0 -4
  26. package/templates/sveltekit-app/src/lib/components/ui/badge/badge.svelte +0 -30
  27. package/templates/sveltekit-app/src/lib/components/ui/badge/index.ts +0 -3
  28. package/templates/sveltekit-app/src/lib/components/ui/button/button.svelte +0 -48
  29. package/templates/sveltekit-app/src/lib/components/ui/button/index.ts +0 -9
  30. package/templates/sveltekit-app/src/lib/components/ui/card/card-content.svelte +0 -18
  31. package/templates/sveltekit-app/src/lib/components/ui/card/card-description.svelte +0 -18
  32. package/templates/sveltekit-app/src/lib/components/ui/card/card-footer.svelte +0 -18
  33. package/templates/sveltekit-app/src/lib/components/ui/card/card-header.svelte +0 -18
  34. package/templates/sveltekit-app/src/lib/components/ui/card/card-title.svelte +0 -18
  35. package/templates/sveltekit-app/src/lib/components/ui/card/card.svelte +0 -21
  36. package/templates/sveltekit-app/src/lib/components/ui/card/index.ts +0 -21
  37. package/templates/sveltekit-app/src/lib/components/ui/index.ts +0 -4
  38. package/templates/sveltekit-app/src/lib/components/ui/input/index.ts +0 -2
  39. package/templates/sveltekit-app/src/lib/components/ui/input/input.svelte +0 -20
  40. package/templates/sveltekit-app/src/lib/permissions.ts +0 -213
  41. package/templates/sveltekit-app/src/lib/utils/index.ts +0 -6
  42. package/templates/sveltekit-app/src/routes/+layout.svelte +0 -8
  43. package/templates/sveltekit-app/src/routes/+page.server.ts +0 -25
  44. package/templates/sveltekit-app/src/routes/+page.svelte +0 -680
  45. package/templates/sveltekit-app/src/routes/workflows/+page.server.ts +0 -23
  46. package/templates/sveltekit-app/src/routes/workflows/+page.svelte +0 -522
  47. package/templates/sveltekit-app/src/server/events.ts +0 -28
  48. package/templates/sveltekit-app/src/server/index.ts +0 -124
  49. package/templates/sveltekit-app/src/server/plugins/auth/auth.test.ts +0 -377
  50. package/templates/sveltekit-app/src/server/plugins/auth/index.ts +0 -815
  51. package/templates/sveltekit-app/src/server/plugins/auth/migrations/001_create_users.ts +0 -25
  52. package/templates/sveltekit-app/src/server/plugins/auth/migrations/002_create_sessions.ts +0 -32
  53. package/templates/sveltekit-app/src/server/plugins/auth/migrations/003_create_refresh_tokens.ts +0 -33
  54. package/templates/sveltekit-app/src/server/plugins/auth/migrations/004_create_passkeys.ts +0 -60
  55. package/templates/sveltekit-app/src/server/plugins/auth/schema.ts +0 -65
  56. package/templates/sveltekit-app/src/server/plugins/demo/index.ts +0 -262
  57. package/templates/sveltekit-app/src/server/plugins/email/email.test.ts +0 -369
  58. package/templates/sveltekit-app/src/server/plugins/email/index.ts +0 -411
  59. package/templates/sveltekit-app/src/server/plugins/email/migrations/001_create_email_tokens.ts +0 -33
  60. package/templates/sveltekit-app/src/server/plugins/email/schema.ts +0 -24
  61. package/templates/sveltekit-app/src/server/plugins/permissions/index.ts +0 -1048
  62. package/templates/sveltekit-app/src/server/plugins/permissions/migrations/001_create_tenants.ts +0 -63
  63. package/templates/sveltekit-app/src/server/plugins/permissions/migrations/002_create_roles.ts +0 -90
  64. package/templates/sveltekit-app/src/server/plugins/permissions/migrations/003_create_resource_grants.ts +0 -50
  65. package/templates/sveltekit-app/src/server/plugins/permissions/permissions.test.ts +0 -566
  66. package/templates/sveltekit-app/src/server/plugins/permissions/schema.ts +0 -67
  67. package/templates/sveltekit-app/src/server/plugins/workflow-demo/index.ts +0 -198
  68. package/templates/sveltekit-app/src/server/routes/auth/auth.schemas.ts +0 -66
  69. package/templates/sveltekit-app/src/server/routes/auth/handlers/login.handler.ts +0 -18
  70. package/templates/sveltekit-app/src/server/routes/auth/handlers/logout.handler.ts +0 -16
  71. package/templates/sveltekit-app/src/server/routes/auth/handlers/me.handler.ts +0 -20
  72. package/templates/sveltekit-app/src/server/routes/auth/handlers/refresh.handler.ts +0 -17
  73. package/templates/sveltekit-app/src/server/routes/auth/handlers/register.handler.ts +0 -19
  74. package/templates/sveltekit-app/src/server/routes/auth/handlers/update-profile.handler.ts +0 -21
  75. package/templates/sveltekit-app/src/server/routes/auth/index.ts +0 -73
  76. package/templates/sveltekit-app/src/server/routes/demo.ts +0 -464
  77. package/templates/sveltekit-app/src/server/routes/example/example.schemas.ts +0 -22
  78. package/templates/sveltekit-app/src/server/routes/example/handlers/greet.handler.ts +0 -21
  79. package/templates/sveltekit-app/src/server/routes/example/index.ts +0 -28
  80. package/templates/sveltekit-app/src/server/routes/permissions/index.ts +0 -248
  81. package/templates/sveltekit-app/src/server/routes/tenants/index.ts +0 -339
  82. package/templates/sveltekit-app/static/robots.txt +0 -3
  83. package/templates/sveltekit-app/svelte.config.ts +0 -17
  84. package/templates/sveltekit-app/tsconfig.json +0 -20
  85. package/templates/sveltekit-app/vite.config.ts +0 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/cli",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
4
4
  "type": "module",
5
5
  "description": "CLI for @donkeylabs/server - project scaffolding and code generation",
6
6
  "main": "./src/index.ts",
@@ -0,0 +1,610 @@
1
+ // packages/cli/src/commands/config.ts
2
+ /**
3
+ * Interactive configuration management
4
+ * Modify plugin configs, deployment settings, etc.
5
+ */
6
+
7
+ import { existsSync, readFileSync, writeFileSync } from "fs";
8
+ import { join } from "path";
9
+ import pc from "picocolors";
10
+
11
+ interface ConfigEntry {
12
+ key: string;
13
+ value: any;
14
+ type: "string" | "number" | "boolean" | "select";
15
+ options?: string[];
16
+ description: string;
17
+ }
18
+
19
+ export async function configCommand(args: string[]) {
20
+ const subcommand = args[0] || "interactive";
21
+
22
+ switch (subcommand) {
23
+ case "list":
24
+ case "ls":
25
+ await listConfig();
26
+ break;
27
+ case "get":
28
+ await getConfigValue(args[1]);
29
+ break;
30
+ case "set":
31
+ await setConfigValue(args[1], args[2]);
32
+ break;
33
+ case "plugins":
34
+ await configurePlugins();
35
+ break;
36
+ case "deployment":
37
+ await configureDeployment();
38
+ break;
39
+ case "database":
40
+ await configureDatabase();
41
+ break;
42
+ case "interactive":
43
+ default:
44
+ await interactiveConfig();
45
+ break;
46
+ }
47
+ }
48
+
49
+ async function interactiveConfig() {
50
+ const prompts = await import("prompts");
51
+
52
+ console.log(pc.cyan(pc.bold("\nāš™ļø Configuration Manager\n")));
53
+
54
+ const action = await prompts.default({
55
+ type: "select",
56
+ name: "value",
57
+ message: "What would you like to configure?",
58
+ choices: [
59
+ { title: "Plugin settings", value: "plugins" },
60
+ { title: "Deployment settings", value: "deployment" },
61
+ { title: "Database configuration", value: "database" },
62
+ { title: "Environment variables", value: "env" },
63
+ { title: "View all config", value: "list" },
64
+ ],
65
+ }).then((r: any) => r.value);
66
+
67
+ switch (action) {
68
+ case "plugins":
69
+ await configurePlugins();
70
+ break;
71
+ case "deployment":
72
+ await configureDeployment();
73
+ break;
74
+ case "database":
75
+ await configureDatabase();
76
+ break;
77
+ case "env":
78
+ await configureEnv();
79
+ break;
80
+ case "list":
81
+ await listConfig();
82
+ break;
83
+ }
84
+ }
85
+
86
+ async function configurePlugins() {
87
+ const prompts = await import("prompts");
88
+ const pluginsDir = join(process.cwd(), "src/server/plugins");
89
+
90
+ if (!existsSync(pluginsDir)) {
91
+ console.log(pc.yellow("No plugins directory found"));
92
+ return;
93
+ }
94
+
95
+ // List installed plugins
96
+ const plugins = require("fs")
97
+ .readdirSync(pluginsDir)
98
+ .filter((f: string) =>
99
+ require("fs").statSync(join(pluginsDir, f)).isDirectory()
100
+ );
101
+
102
+ if (plugins.length === 0) {
103
+ console.log(pc.yellow("No plugins installed"));
104
+ return;
105
+ }
106
+
107
+ const pluginName = await prompts.default({
108
+ type: "select",
109
+ name: "value",
110
+ message: "Select plugin to configure:",
111
+ choices: plugins.map((p: string) => ({ title: p, value: p })),
112
+ }).then((r: any) => r.value);
113
+
114
+ // Load plugin config schema
115
+ const pluginConfigPath = join(pluginsDir, pluginName, "config.ts");
116
+ if (!existsSync(pluginConfigPath)) {
117
+ console.log(pc.yellow(`Plugin ${pluginName} has no configurable options`));
118
+ return;
119
+ }
120
+
121
+ // Interactive config editor
122
+ console.log(pc.cyan(`\nConfiguring ${pluginName}...\n`));
123
+
124
+ // Parse and edit config file
125
+ const configContent = readFileSync(pluginConfigPath, "utf-8");
126
+ console.log(pc.gray("Current config:"));
127
+ console.log(configContent);
128
+
129
+ // Would implement full config editing here
130
+ console.log(pc.yellow("Config editing coming soon!"));
131
+ }
132
+
133
+ async function configureDeployment() {
134
+ const prompts = await import("prompts");
135
+
136
+ console.log(pc.cyan(pc.bold("\nšŸš€ Deployment Configuration\n")));
137
+
138
+ const platform = await prompts.default({
139
+ type: "select",
140
+ name: "value",
141
+ message: "Select platform:",
142
+ choices: [
143
+ { title: "Vercel", value: "vercel" },
144
+ { title: "Cloudflare Workers", value: "cloudflare" },
145
+ { title: "AWS Lambda", value: "aws" },
146
+ { title: "VPS (Docker)", value: "docker" },
147
+ ],
148
+ }).then((r: any) => r.value);
149
+
150
+ switch (platform) {
151
+ case "vercel":
152
+ await configureVercel();
153
+ break;
154
+ case "cloudflare":
155
+ await configureCloudflare();
156
+ break;
157
+ case "aws":
158
+ await configureAWS();
159
+ break;
160
+ case "docker":
161
+ await configureDocker();
162
+ break;
163
+ }
164
+ }
165
+
166
+ async function configureVercel() {
167
+ const prompts = await import("prompts");
168
+
169
+ console.log(pc.cyan("\nConfiguring Vercel deployment...\n"));
170
+
171
+ const settings = await prompts.default([
172
+ {
173
+ type: "confirm",
174
+ name: "enableAnalytics",
175
+ message: "Enable Vercel Analytics?",
176
+ initial: false,
177
+ },
178
+ {
179
+ type: "confirm",
180
+ name: "enableSpeedInsights",
181
+ message: "Enable Speed Insights?",
182
+ initial: false,
183
+ },
184
+ {
185
+ type: "select",
186
+ name: "region",
187
+ message: "Deployment region:",
188
+ choices: [
189
+ { title: "Auto (default)", value: "auto" },
190
+ { title: "US East", value: "iad1" },
191
+ { title: "US West", value: "sfo1" },
192
+ { title: "EU West", value: "fra1" },
193
+ { title: "AP East", value: "hkg1" },
194
+ ],
195
+ },
196
+ ]);
197
+
198
+ // Update vercel.json
199
+ const vercelConfig = {
200
+ version: 2,
201
+ builds: [
202
+ {
203
+ src: "api/index.ts",
204
+ use: "@vercel/node",
205
+ },
206
+ ],
207
+ regions: settings.region === "auto" ? undefined : [settings.region],
208
+ analytics: settings.enableAnalytics,
209
+ speedInsights: settings.enableSpeedInsights,
210
+ };
211
+
212
+ writeFileSync(
213
+ join(process.cwd(), "vercel.json"),
214
+ JSON.stringify(vercelConfig, null, 2)
215
+ );
216
+
217
+ console.log(pc.green("āœ… Vercel configuration updated"));
218
+ }
219
+
220
+ async function configureCloudflare() {
221
+ const prompts = await import("prompts");
222
+
223
+ console.log(pc.cyan("\nConfiguring Cloudflare Workers...\n"));
224
+
225
+ const settings = await prompts.default([
226
+ {
227
+ type: "text",
228
+ name: "name",
229
+ message: "Worker name:",
230
+ initial: "my-app",
231
+ },
232
+ {
233
+ type: "select",
234
+ name: "usageModel",
235
+ message: "Usage model:",
236
+ choices: [
237
+ { title: "Bundled (default)", value: "bundled" },
238
+ { title: "Unbound", value: "unbound" },
239
+ ],
240
+ },
241
+ {
242
+ type: "confirm",
243
+ name: "enableAnalytics",
244
+ message: "Enable Cloudflare Analytics?",
245
+ initial: true,
246
+ },
247
+ ]);
248
+
249
+ // Update wrangler.toml
250
+ const wranglerConfig = `name = "${settings.name}"
251
+ main = "src/index.ts"
252
+ compatibility_date = "2024-01-01"
253
+ usage_model = "${settings.usageModel}"
254
+ analytics_engine_datasets = [{ binding = "ANALYTICS", dataset = "${settings.name}_analytics" }]
255
+
256
+ [env.production]
257
+ vars = { ENVIRONMENT = "production" }
258
+
259
+ [env.staging]
260
+ vars = { ENVIRONMENT = "staging" }
261
+ `;
262
+
263
+ writeFileSync(join(process.cwd(), "wrangler.toml"), wranglerConfig);
264
+ console.log(pc.green("āœ… Cloudflare Workers configuration updated"));
265
+ }
266
+
267
+ async function configureAWS() {
268
+ const prompts = await import("prompts");
269
+
270
+ console.log(pc.cyan("\nConfiguring AWS Lambda...\n"));
271
+
272
+ const settings = await prompts.default([
273
+ {
274
+ type: "text",
275
+ name: "stackName",
276
+ message: "CloudFormation stack name:",
277
+ initial: "my-app",
278
+ },
279
+ {
280
+ type: "select",
281
+ name: "region",
282
+ message: "AWS Region:",
283
+ choices: [
284
+ { title: "us-east-1 (N. Virginia)", value: "us-east-1" },
285
+ { title: "us-west-2 (Oregon)", value: "us-west-2" },
286
+ { title: "eu-west-1 (Ireland)", value: "eu-west-1" },
287
+ { title: "ap-southeast-1 (Singapore)", value: "ap-southeast-1" },
288
+ ],
289
+ },
290
+ {
291
+ type: "number",
292
+ name: "memorySize",
293
+ message: "Lambda memory (MB):",
294
+ initial: 512,
295
+ },
296
+ {
297
+ type: "number",
298
+ name: "timeout",
299
+ message: "Lambda timeout (seconds):",
300
+ initial: 30,
301
+ },
302
+ ]);
303
+
304
+ // Create SAM template
305
+ const samTemplate = {
306
+ AWSTemplateFormatVersion: "2010-09-09",
307
+ Transform: "AWS::Serverless-2016-10-31",
308
+ Description: `${settings.stackName} API`,
309
+ Globals: {
310
+ Function: {
311
+ Timeout: settings.timeout,
312
+ MemorySize: settings.memorySize,
313
+ Runtime: "nodejs20.x",
314
+ Architectures: ["arm64"],
315
+ },
316
+ },
317
+ Resources: {
318
+ ApiFunction: {
319
+ Type: "AWS::Serverless::Function",
320
+ Properties: {
321
+ FunctionName: settings.stackName,
322
+ Handler: "dist/index.handler",
323
+ CodeUri: "./",
324
+ Events: {
325
+ ApiEvent: {
326
+ Type: "Api",
327
+ Properties: {
328
+ Path: "/{proxy+}",
329
+ Method: "ANY",
330
+ },
331
+ },
332
+ },
333
+ },
334
+ },
335
+ },
336
+ Outputs: {
337
+ ApiUrl: {
338
+ Description: "API Gateway endpoint URL",
339
+ Value: { "Fn::Sub": "https://\${ServerlessRestApi}.execute-api.\${AWS::Region}.amazonaws.com/Prod/" },
340
+ },
341
+ },
342
+ };
343
+
344
+ writeFileSync(
345
+ join(process.cwd(), "template.yaml"),
346
+ JSON.stringify(samTemplate, null, 2)
347
+ );
348
+ console.log(pc.green("āœ… AWS SAM template created"));
349
+ }
350
+
351
+ async function configureDocker() {
352
+ const prompts = await import("prompts");
353
+
354
+ console.log(pc.cyan("\nConfiguring Docker deployment...\n"));
355
+
356
+ const settings = await prompts.default([
357
+ {
358
+ type: "confirm",
359
+ name: "useNginx",
360
+ message: "Include Nginx reverse proxy?",
361
+ initial: true,
362
+ },
363
+ {
364
+ type: "confirm",
365
+ name: "enableSSL",
366
+ message: "Enable SSL/Let's Encrypt?",
367
+ initial: true,
368
+ },
369
+ {
370
+ type: "confirm",
371
+ name: "enableWatchtower",
372
+ message: "Enable Watchtower for auto-updates?",
373
+ initial: true,
374
+ },
375
+ ]);
376
+
377
+ console.log(pc.green("āœ… Docker configuration updated"));
378
+ console.log(pc.gray("Run 'docker-compose up -d' to deploy"));
379
+ }
380
+
381
+ async function configureDatabase() {
382
+ const prompts = await import("prompts");
383
+
384
+ console.log(pc.cyan(pc.bold("\nšŸ—„ļø Database Configuration\n")));
385
+
386
+ const dbType = await prompts.default({
387
+ type: "select",
388
+ name: "value",
389
+ message: "Database type:",
390
+ choices: [
391
+ { title: "SQLite", value: "sqlite" },
392
+ { title: "PostgreSQL", value: "postgres" },
393
+ { title: "MySQL", value: "mysql" },
394
+ ],
395
+ }).then((r: any) => r.value);
396
+
397
+ const connectionString = await prompts.default({
398
+ type: "text",
399
+ name: "value",
400
+ message: "Database connection string:",
401
+ initial:
402
+ dbType === "sqlite"
403
+ ? "./data/app.db"
404
+ : dbType === "postgres"
405
+ ? "postgresql://user:pass@localhost:5432/app"
406
+ : "mysql://user:pass@localhost:3306/app",
407
+ }).then((r: any) => r.value);
408
+
409
+ // Update .env
410
+ const envPath = join(process.cwd(), ".env");
411
+ let envContent = "";
412
+
413
+ if (existsSync(envPath)) {
414
+ envContent = readFileSync(envPath, "utf-8");
415
+ // Replace or add DATABASE_URL
416
+ if (envContent.includes("DATABASE_URL=")) {
417
+ envContent = envContent.replace(
418
+ /DATABASE_URL=.*/,
419
+ `DATABASE_URL=${connectionString}`
420
+ );
421
+ } else {
422
+ envContent += `\nDATABASE_URL=${connectionString}\n`;
423
+ }
424
+ } else {
425
+ envContent = `DATABASE_URL=${connectionString}\n`;
426
+ }
427
+
428
+ writeFileSync(envPath, envContent);
429
+ console.log(pc.green("āœ… Database configuration updated in .env"));
430
+ }
431
+
432
+ async function configureEnv() {
433
+ const prompts = await import("prompts");
434
+
435
+ console.log(pc.cyan(pc.bold("\nšŸ” Environment Variables\n")));
436
+
437
+ const envPath = join(process.cwd(), ".env");
438
+ let envVars: Record<string, string> = {};
439
+
440
+ if (existsSync(envPath)) {
441
+ const content = readFileSync(envPath, "utf-8");
442
+ for (const line of content.split("\n")) {
443
+ const match = line.match(/^([A-Z_]+)=(.*)$/);
444
+ if (match) {
445
+ envVars[match[1]] = match[2];
446
+ }
447
+ }
448
+ }
449
+
450
+ const action = await prompts.default({
451
+ type: "select",
452
+ name: "value",
453
+ message: "Action:",
454
+ choices: [
455
+ { title: "Add new variable", value: "add" },
456
+ { title: "Edit existing", value: "edit" },
457
+ { title: "Delete variable", value: "delete" },
458
+ { title: "View all", value: "view" },
459
+ ],
460
+ }).then((r: any) => r.value);
461
+
462
+ switch (action) {
463
+ case "add":
464
+ const newKey = await prompts.default({
465
+ type: "text",
466
+ name: "value",
467
+ message: "Variable name:",
468
+ validate: (v: string) => /^[A-Z_]+$/.test(v) || "Use UPPER_CASE with underscores",
469
+ }).then((r: any) => r.value);
470
+
471
+ const newValue = await prompts.default({
472
+ type: "text",
473
+ name: "value",
474
+ message: "Value:",
475
+ }).then((r: any) => r.value);
476
+
477
+ envVars[newKey] = newValue;
478
+ break;
479
+
480
+ case "edit":
481
+ const editKey = await prompts.default({
482
+ type: "select",
483
+ name: "value",
484
+ message: "Select variable to edit:",
485
+ choices: Object.keys(envVars).map((k) => ({ title: k, value: k })),
486
+ }).then((r: any) => r.value);
487
+
488
+ const editValue = await prompts.default({
489
+ type: "text",
490
+ name: "value",
491
+ message: "New value:",
492
+ initial: envVars[editKey],
493
+ }).then((r: any) => r.value);
494
+
495
+ envVars[editKey] = editValue;
496
+ break;
497
+
498
+ case "delete":
499
+ const deleteKey = await prompts.default({
500
+ type: "select",
501
+ name: "value",
502
+ message: "Select variable to delete:",
503
+ choices: Object.keys(envVars).map((k) => ({ title: k, value: k })),
504
+ }).then((r: any) => r.value);
505
+
506
+ delete envVars[deleteKey];
507
+ break;
508
+
509
+ case "view":
510
+ console.log(pc.cyan("\nCurrent environment variables:\n"));
511
+ for (const [key, value] of Object.entries(envVars)) {
512
+ const maskedValue = key.includes("SECRET") || key.includes("PASSWORD")
513
+ ? "***"
514
+ : value;
515
+ console.log(` ${key}=${maskedValue}`);
516
+ }
517
+ return;
518
+ }
519
+
520
+ // Save changes
521
+ const newContent = Object.entries(envVars)
522
+ .map(([k, v]) => `${k}=${v}`)
523
+ .join("\n");
524
+ writeFileSync(envPath, newContent + "\n");
525
+ console.log(pc.green("āœ… Environment variables updated"));
526
+ }
527
+
528
+ async function listConfig() {
529
+ console.log(pc.cyan(pc.bold("\nšŸ“‹ Current Configuration\n")));
530
+
531
+ // Show deployment config
532
+ const hasVercel = existsSync(join(process.cwd(), "vercel.json"));
533
+ const hasCloudflare = existsSync(join(process.cwd(), "wrangler.toml"));
534
+ const hasAWS = existsSync(join(process.cwd(), "template.yaml"));
535
+ const hasDocker = existsSync(join(process.cwd(), "Dockerfile"));
536
+
537
+ console.log(pc.bold("Deployment:"));
538
+ if (hasVercel) console.log(` ${pc.green("āœ“")} Vercel`);
539
+ if (hasCloudflare) console.log(` ${pc.green("āœ“")} Cloudflare Workers`);
540
+ if (hasAWS) console.log(` ${pc.green("āœ“")} AWS Lambda`);
541
+ if (hasDocker) console.log(` ${pc.green("āœ“")} Docker`);
542
+
543
+ // Show plugins
544
+ const pluginsDir = join(process.cwd(), "src/server/plugins");
545
+ if (existsSync(pluginsDir)) {
546
+ const plugins = require("fs")
547
+ .readdirSync(pluginsDir)
548
+ .filter((f: string) =>
549
+ require("fs").statSync(join(pluginsDir, f)).isDirectory()
550
+ );
551
+ console.log(pc.bold("\nPlugins:") + ` ${plugins.join(", ")}`);
552
+ }
553
+
554
+ // Show env
555
+ const envPath = join(process.cwd(), ".env");
556
+ if (existsSync(envPath)) {
557
+ console.log(pc.bold("\nEnvironment:"));
558
+ const content = readFileSync(envPath, "utf-8");
559
+ const dbMatch = content.match(/DATABASE_URL=(.+)/);
560
+ if (dbMatch) {
561
+ console.log(` Database: ${dbMatch[1].split("://")[0]}`);
562
+ }
563
+ }
564
+ }
565
+
566
+ async function getConfigValue(key: string) {
567
+ if (!key) {
568
+ console.log(pc.red("Usage: donkeylabs config get <key>"));
569
+ return;
570
+ }
571
+
572
+ const envPath = join(process.cwd(), ".env");
573
+ if (!existsSync(envPath)) {
574
+ console.log(pc.yellow("No .env file found"));
575
+ return;
576
+ }
577
+
578
+ const content = readFileSync(envPath, "utf-8");
579
+ const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
580
+
581
+ if (match) {
582
+ console.log(`${key}=${match[1]}`);
583
+ } else {
584
+ console.log(pc.yellow(`Key '${key}' not found`));
585
+ }
586
+ }
587
+
588
+ async function setConfigValue(key: string, value: string) {
589
+ if (!key || !value) {
590
+ console.log(pc.red("Usage: donkeylabs config set <key> <value>"));
591
+ return;
592
+ }
593
+
594
+ const envPath = join(process.cwd(), ".env");
595
+ let content = "";
596
+
597
+ if (existsSync(envPath)) {
598
+ content = readFileSync(envPath, "utf-8");
599
+ if (content.includes(`${key}=`)) {
600
+ content = content.replace(new RegExp(`${key}=.+`), `${key}=${value}`);
601
+ } else {
602
+ content += `\n${key}=${value}\n`;
603
+ }
604
+ } else {
605
+ content = `${key}=${value}\n`;
606
+ }
607
+
608
+ writeFileSync(envPath, content);
609
+ console.log(pc.green(`āœ… Set ${key}=${value}`));
610
+ }