@buenojs/bueno 0.8.4 → 0.8.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.
Files changed (234) hide show
  1. package/README.md +264 -17
  2. package/dist/cli/{index.js → bin.js} +413 -332
  3. package/dist/container/index.js +273 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/graphql/index.js +2156 -0
  8. package/dist/health/index.js +364 -0
  9. package/dist/i18n/index.js +345 -0
  10. package/dist/index.js +9694 -5047
  11. package/dist/jobs/index.js +819 -0
  12. package/dist/lock/index.js +367 -0
  13. package/dist/logger/index.js +281 -0
  14. package/dist/metrics/index.js +289 -0
  15. package/dist/middleware/index.js +77 -0
  16. package/dist/migrations/index.js +571 -0
  17. package/dist/modules/index.js +3411 -0
  18. package/dist/notification/index.js +484 -0
  19. package/dist/observability/index.js +331 -0
  20. package/dist/openapi/index.js +795 -0
  21. package/dist/orm/index.js +1356 -0
  22. package/dist/router/index.js +886 -0
  23. package/dist/rpc/index.js +691 -0
  24. package/dist/schema/index.js +400 -0
  25. package/dist/telemetry/index.js +595 -0
  26. package/dist/template/index.js +640 -0
  27. package/dist/templates/index.js +640 -0
  28. package/dist/testing/index.js +1111 -0
  29. package/dist/types/index.js +60 -0
  30. package/llms.txt +231 -0
  31. package/package.json +125 -27
  32. package/src/cache/index.ts +2 -1
  33. package/src/cli/ARCHITECTURE.md +3 -3
  34. package/src/cli/bin.ts +2 -2
  35. package/src/cli/commands/build.ts +183 -165
  36. package/src/cli/commands/dev.ts +96 -89
  37. package/src/cli/commands/generate.ts +142 -111
  38. package/src/cli/commands/help.ts +20 -16
  39. package/src/cli/commands/index.ts +3 -6
  40. package/src/cli/commands/migration.ts +124 -105
  41. package/src/cli/commands/new.ts +294 -232
  42. package/src/cli/commands/start.ts +81 -79
  43. package/src/cli/core/args.ts +68 -50
  44. package/src/cli/core/console.ts +89 -95
  45. package/src/cli/core/index.ts +4 -4
  46. package/src/cli/core/prompt.ts +65 -62
  47. package/src/cli/core/spinner.ts +23 -20
  48. package/src/cli/index.ts +46 -38
  49. package/src/cli/templates/database/index.ts +37 -18
  50. package/src/cli/templates/database/mysql.ts +3 -3
  51. package/src/cli/templates/database/none.ts +2 -2
  52. package/src/cli/templates/database/postgresql.ts +3 -3
  53. package/src/cli/templates/database/sqlite.ts +3 -3
  54. package/src/cli/templates/deploy.ts +29 -26
  55. package/src/cli/templates/docker.ts +41 -30
  56. package/src/cli/templates/frontend/index.ts +33 -15
  57. package/src/cli/templates/frontend/none.ts +2 -2
  58. package/src/cli/templates/frontend/react.ts +18 -18
  59. package/src/cli/templates/frontend/solid.ts +15 -15
  60. package/src/cli/templates/frontend/svelte.ts +17 -17
  61. package/src/cli/templates/frontend/vue.ts +15 -15
  62. package/src/cli/templates/generators/index.ts +29 -29
  63. package/src/cli/templates/generators/types.ts +21 -21
  64. package/src/cli/templates/index.ts +6 -6
  65. package/src/cli/templates/project/api.ts +37 -36
  66. package/src/cli/templates/project/default.ts +25 -25
  67. package/src/cli/templates/project/fullstack.ts +28 -26
  68. package/src/cli/templates/project/index.ts +55 -16
  69. package/src/cli/templates/project/minimal.ts +17 -12
  70. package/src/cli/templates/project/types.ts +10 -5
  71. package/src/cli/templates/project/website.ts +15 -15
  72. package/src/cli/utils/fs.ts +55 -41
  73. package/src/cli/utils/index.ts +3 -3
  74. package/src/cli/utils/strings.ts +47 -33
  75. package/src/cli/utils/version.ts +14 -8
  76. package/src/config/env-validation.ts +100 -0
  77. package/src/config/env.ts +169 -41
  78. package/src/config/index.ts +28 -20
  79. package/src/config/loader.ts +25 -16
  80. package/src/config/merge.ts +21 -10
  81. package/src/config/types.ts +566 -25
  82. package/src/config/validation.ts +215 -7
  83. package/src/container/forward-ref.ts +22 -22
  84. package/src/container/index.ts +34 -12
  85. package/src/context/index.ts +11 -1
  86. package/src/database/index.ts +7 -190
  87. package/src/database/orm/builder.ts +457 -0
  88. package/src/database/orm/casts/index.ts +130 -0
  89. package/src/database/orm/casts/types.ts +25 -0
  90. package/src/database/orm/compiler.ts +304 -0
  91. package/src/database/orm/hooks/index.ts +114 -0
  92. package/src/database/orm/index.ts +61 -0
  93. package/src/database/orm/model-registry.ts +59 -0
  94. package/src/database/orm/model.ts +821 -0
  95. package/src/database/orm/relationships/base.ts +146 -0
  96. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  97. package/src/database/orm/relationships/belongs-to.ts +56 -0
  98. package/src/database/orm/relationships/has-many.ts +45 -0
  99. package/src/database/orm/relationships/has-one.ts +41 -0
  100. package/src/database/orm/relationships/index.ts +11 -0
  101. package/src/database/orm/scopes/index.ts +55 -0
  102. package/src/events/__tests__/event-system.test.ts +235 -0
  103. package/src/events/config.ts +238 -0
  104. package/src/events/example-usage.ts +185 -0
  105. package/src/events/index.ts +278 -0
  106. package/src/events/manager.ts +385 -0
  107. package/src/events/registry.ts +182 -0
  108. package/src/events/types.ts +124 -0
  109. package/src/frontend/api-routes.ts +65 -23
  110. package/src/frontend/bundler.ts +76 -34
  111. package/src/frontend/console-client.ts +2 -2
  112. package/src/frontend/console-stream.ts +94 -38
  113. package/src/frontend/dev-server.ts +94 -46
  114. package/src/frontend/file-router.ts +61 -19
  115. package/src/frontend/frameworks/index.ts +37 -10
  116. package/src/frontend/frameworks/react.ts +10 -8
  117. package/src/frontend/frameworks/solid.ts +11 -9
  118. package/src/frontend/frameworks/svelte.ts +15 -9
  119. package/src/frontend/frameworks/vue.ts +13 -11
  120. package/src/frontend/hmr-client.ts +12 -10
  121. package/src/frontend/hmr.ts +146 -103
  122. package/src/frontend/index.ts +14 -5
  123. package/src/frontend/islands.ts +41 -22
  124. package/src/frontend/isr.ts +59 -37
  125. package/src/frontend/layout.ts +36 -21
  126. package/src/frontend/ssr/react.ts +74 -27
  127. package/src/frontend/ssr/solid.ts +54 -20
  128. package/src/frontend/ssr/svelte.ts +48 -14
  129. package/src/frontend/ssr/vue.ts +50 -18
  130. package/src/frontend/ssr.ts +83 -39
  131. package/src/frontend/types.ts +91 -56
  132. package/src/graphql/built-in-engine.ts +598 -0
  133. package/src/graphql/context-builder.ts +110 -0
  134. package/src/graphql/decorators.ts +358 -0
  135. package/src/graphql/execution-pipeline.ts +227 -0
  136. package/src/graphql/graphql-module.ts +563 -0
  137. package/src/graphql/index.ts +101 -0
  138. package/src/graphql/metadata.ts +237 -0
  139. package/src/graphql/schema-builder.ts +319 -0
  140. package/src/graphql/subscription-handler.ts +283 -0
  141. package/src/graphql/types.ts +324 -0
  142. package/src/health/index.ts +21 -9
  143. package/src/i18n/engine.ts +305 -0
  144. package/src/i18n/index.ts +38 -0
  145. package/src/i18n/loader.ts +218 -0
  146. package/src/i18n/middleware.ts +164 -0
  147. package/src/i18n/negotiator.ts +162 -0
  148. package/src/i18n/types.ts +158 -0
  149. package/src/index.ts +182 -27
  150. package/src/jobs/drivers/memory.ts +315 -0
  151. package/src/jobs/drivers/redis.ts +459 -0
  152. package/src/jobs/index.ts +30 -0
  153. package/src/jobs/queue.ts +281 -0
  154. package/src/jobs/types.ts +295 -0
  155. package/src/jobs/worker.ts +380 -0
  156. package/src/logger/index.ts +1 -3
  157. package/src/logger/transports/index.ts +62 -22
  158. package/src/metrics/index.ts +25 -16
  159. package/src/migrations/index.ts +9 -0
  160. package/src/modules/filters.ts +13 -17
  161. package/src/modules/guards.ts +49 -26
  162. package/src/modules/index.ts +457 -299
  163. package/src/modules/interceptors.ts +58 -20
  164. package/src/modules/lazy.ts +11 -19
  165. package/src/modules/lifecycle.ts +15 -7
  166. package/src/modules/metadata.ts +15 -5
  167. package/src/modules/pipes.ts +94 -72
  168. package/src/notification/channels/base.ts +68 -0
  169. package/src/notification/channels/email.ts +105 -0
  170. package/src/notification/channels/push.ts +104 -0
  171. package/src/notification/channels/sms.ts +105 -0
  172. package/src/notification/channels/whatsapp.ts +104 -0
  173. package/src/notification/index.ts +48 -0
  174. package/src/notification/service.ts +354 -0
  175. package/src/notification/types.ts +344 -0
  176. package/src/observability/__tests__/observability.test.ts +483 -0
  177. package/src/observability/breadcrumbs.ts +114 -0
  178. package/src/observability/index.ts +136 -0
  179. package/src/observability/interceptor.ts +85 -0
  180. package/src/observability/service.ts +303 -0
  181. package/src/observability/trace.ts +37 -0
  182. package/src/observability/types.ts +196 -0
  183. package/src/openapi/__tests__/decorators.test.ts +335 -0
  184. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  185. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  186. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  187. package/src/openapi/decorators.ts +328 -0
  188. package/src/openapi/document-builder.ts +274 -0
  189. package/src/openapi/index.ts +112 -0
  190. package/src/openapi/metadata.ts +112 -0
  191. package/src/openapi/route-scanner.ts +289 -0
  192. package/src/openapi/schema-generator.ts +256 -0
  193. package/src/openapi/swagger-module.ts +166 -0
  194. package/src/openapi/types.ts +398 -0
  195. package/src/orm/index.ts +10 -0
  196. package/src/rpc/index.ts +3 -1
  197. package/src/schema/index.ts +9 -0
  198. package/src/security/index.ts +15 -6
  199. package/src/ssg/index.ts +9 -8
  200. package/src/telemetry/index.ts +76 -22
  201. package/src/template/index.ts +7 -0
  202. package/src/templates/engine.ts +224 -0
  203. package/src/templates/index.ts +9 -0
  204. package/src/templates/loader.ts +331 -0
  205. package/src/templates/renderers/markdown.ts +212 -0
  206. package/src/templates/renderers/simple.ts +269 -0
  207. package/src/templates/types.ts +154 -0
  208. package/src/testing/index.ts +100 -27
  209. package/src/types/optional-deps.d.ts +347 -187
  210. package/src/validation/index.ts +92 -2
  211. package/src/validation/schemas.ts +536 -0
  212. package/tests/integration/cli.test.ts +19 -19
  213. package/tests/integration/fullstack.test.ts +4 -4
  214. package/tests/unit/cli.test.ts +1 -1
  215. package/tests/unit/database.test.ts +2 -72
  216. package/tests/unit/env-validation.test.ts +166 -0
  217. package/tests/unit/events.test.ts +910 -0
  218. package/tests/unit/graphql.test.ts +991 -0
  219. package/tests/unit/i18n.test.ts +455 -0
  220. package/tests/unit/jobs.test.ts +493 -0
  221. package/tests/unit/notification.test.ts +988 -0
  222. package/tests/unit/observability.test.ts +453 -0
  223. package/tests/unit/orm/builder.test.ts +323 -0
  224. package/tests/unit/orm/casts.test.ts +179 -0
  225. package/tests/unit/orm/compiler.test.ts +220 -0
  226. package/tests/unit/orm/eager-loading.test.ts +285 -0
  227. package/tests/unit/orm/hooks.test.ts +191 -0
  228. package/tests/unit/orm/model.test.ts +373 -0
  229. package/tests/unit/orm/relationships.test.ts +303 -0
  230. package/tests/unit/orm/scopes.test.ts +74 -0
  231. package/tests/unit/templates-simple.test.ts +53 -0
  232. package/tests/unit/templates.test.ts +454 -0
  233. package/tests/unit/validation.test.ts +18 -24
  234. package/tsconfig.json +11 -3
@@ -4,54 +4,54 @@
4
4
  * Create a new Bueno project
5
5
  */
6
6
 
7
- import { defineCommand } from './index';
8
- import { getOption, hasFlag, getOptionValues, type ParsedArgs } from '../core/args';
9
- import { cliConsole, colors, printTable } from '../core/console';
10
- import { prompt, select, isInteractive } from '../core/prompt';
11
- import { spinner, runTasks, type TaskOptions } from '../core/spinner';
12
7
  import {
13
- fileExists,
14
- writeFile,
15
- createDirectory,
16
- joinPaths,
17
- } from '../utils/fs';
18
- import { kebabCase } from '../utils/strings';
19
- import { getBuenoDependency } from '../utils/version';
20
- import { CLIError, CLIErrorType } from '../index';
8
+ type ParsedArgs,
9
+ getOption,
10
+ getOptionValues,
11
+ hasFlag,
12
+ } from "../core/args";
13
+ import { cliConsole, colors, printTable } from "../core/console";
14
+ import { isInteractive, prompt, select } from "../core/prompt";
15
+ import { type TaskOptions, runTasks, spinner } from "../core/spinner";
16
+ import { CLIError, CLIErrorType } from "../index";
21
17
  import {
22
18
  type DeployPlatform,
23
- getDeployTemplate,
24
19
  getDeployFilename,
25
20
  getDeployPlatformName,
26
- } from '../templates';
21
+ getDeployTemplate,
22
+ } from "../templates";
27
23
  import {
28
- type ProjectTemplate,
29
- type ProjectConfig,
30
- type FrontendFramework,
31
24
  type DatabaseDriver,
32
- getTemplateOptions,
25
+ type FrontendFramework,
26
+ type ProjectConfig,
27
+ type ProjectTemplate,
33
28
  getDatabaseOptions,
34
29
  getFrontendOptions,
35
- } from '../templates';
30
+ getTemplateOptions,
31
+ } from "../templates";
32
+ import { createDirectory, fileExists, joinPaths, writeFile } from "../utils/fs";
33
+ import { kebabCase } from "../utils/strings";
34
+ import { getBuenoDependency } from "../utils/version";
35
+ import { defineCommand } from "./index";
36
36
 
37
37
  /**
38
38
  * Validate project name
39
39
  */
40
40
  function validateProjectName(name: string): boolean | string {
41
41
  if (!name || name.length === 0) {
42
- return 'Project name is required';
42
+ return "Project name is required";
43
43
  }
44
44
 
45
45
  if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
46
- return 'Project name can only contain letters, numbers, hyphens, and underscores';
46
+ return "Project name can only contain letters, numbers, hyphens, and underscores";
47
47
  }
48
48
 
49
- if (name.startsWith('-') || name.startsWith('_')) {
50
- return 'Project name cannot start with a hyphen or underscore';
49
+ if (name.startsWith("-") || name.startsWith("_")) {
50
+ return "Project name cannot start with a hyphen or underscore";
51
51
  }
52
52
 
53
53
  if (name.length > 100) {
54
- return 'Project name is too long (max 100 characters)';
54
+ return "Project name is too long (max 100 characters)";
55
55
  }
56
56
 
57
57
  return true;
@@ -60,36 +60,43 @@ function validateProjectName(name: string): boolean | string {
60
60
  /**
61
61
  * Get package.json template
62
62
  */
63
- function getPackageJsonTemplate(config: ProjectConfig, template: { dependencies?: Record<string, string>; devDependencies?: Record<string, string>; scripts?: Record<string, string> }): string {
63
+ function getPackageJsonTemplate(
64
+ config: ProjectConfig,
65
+ template: {
66
+ dependencies?: Record<string, string>;
67
+ devDependencies?: Record<string, string>;
68
+ scripts?: Record<string, string>;
69
+ },
70
+ ): string {
64
71
  const dependencies: Record<string, string> = {
65
72
  ...getBuenoDependency(),
66
73
  ...(template.dependencies || {}),
67
74
  };
68
-
75
+
69
76
  // If using link, don't add @buenojs/bueno to dependencies
70
77
  if (config.link) {
71
- delete dependencies['@buenojs/bueno'];
78
+ delete dependencies["@buenojs/bueno"];
72
79
  }
73
80
 
74
81
  const devDependencies: Record<string, string> = {
75
- '@types/bun': 'latest',
76
- typescript: '^5.3.0',
82
+ "@types/bun": "latest",
83
+ typescript: "^5.3.0",
77
84
  ...(template.devDependencies || {}),
78
85
  };
79
86
 
80
87
  const scripts: Record<string, string> = {
81
- dev: 'bun run --watch server/main.ts',
82
- build: 'bun build ./server/main.ts --outdir ./dist --target bun',
83
- start: 'bun run dist/main.js',
84
- test: 'bun test',
88
+ dev: "bun run --watch server/main.ts",
89
+ build: "bun build ./server/main.ts --outdir ./dist --target bun",
90
+ start: "bun run dist/main.js",
91
+ test: "bun test",
85
92
  ...(template.scripts || {}),
86
93
  };
87
94
 
88
95
  return JSON.stringify(
89
96
  {
90
97
  name: kebabCase(config.name),
91
- version: '0.1.0',
92
- type: 'module',
98
+ version: "0.1.0",
99
+ type: "module",
93
100
  scripts,
94
101
  dependencies,
95
102
  devDependencies,
@@ -106,20 +113,20 @@ function getTsConfigTemplate(): string {
106
113
  return JSON.stringify(
107
114
  {
108
115
  compilerOptions: {
109
- target: 'ESNext',
110
- module: 'ESNext',
111
- moduleResolution: 'bundler',
116
+ target: "ESNext",
117
+ module: "ESNext",
118
+ moduleResolution: "bundler",
112
119
  strict: true,
113
120
  skipLibCheck: true,
114
121
  esModuleInterop: true,
115
122
  allowSyntheticDefaultImports: true,
116
- jsx: 'react-jsx',
123
+ jsx: "react-jsx",
117
124
  paths: {
118
- '@buenojs/bueno': ['./node_modules/@buenojs/bueno/dist/index.d.ts'],
119
- },
125
+ "@buenojs/bueno": ["./node_modules/@buenojs/bueno/dist/index.d.ts"],
126
+ },
120
127
  },
121
- include: ['server/**/*', 'client/**/*'],
122
- exclude: ['node_modules', 'dist'],
128
+ include: ["server/**/*", "client/**/*"],
129
+ exclude: ["node_modules", "dist"],
123
130
  },
124
131
  null,
125
132
  2,
@@ -130,7 +137,7 @@ function getTsConfigTemplate(): string {
130
137
  * Get .env.example template
131
138
  */
132
139
  function getEnvExampleTemplate(config: ProjectConfig): string {
133
- if (config.database === 'none' || config.database === 'sqlite') {
140
+ if (config.database === "none" || config.database === "sqlite") {
134
141
  return `# Bueno Environment Variables
135
142
  NODE_ENV=development
136
143
  `;
@@ -186,11 +193,11 @@ coverage/
186
193
  */
187
194
  function getReadmeTemplate(config: ProjectConfig): string {
188
195
  const templateDescriptions: Record<ProjectTemplate, string> = {
189
- default: 'Standard project with modules and database',
190
- minimal: 'Bare minimum project structure',
191
- fullstack: 'Full-stack project with SSR and frontend',
192
- api: 'API-only project without frontend',
193
- website: 'Static website with SSG',
196
+ default: "Standard project with modules and database",
197
+ minimal: "Bare minimum project structure",
198
+ fullstack: "Full-stack project with SSR and frontend",
199
+ api: "API-only project without frontend",
200
+ website: "Static website with SSG",
194
201
  };
195
202
 
196
203
  return `# ${config.name}
@@ -236,13 +243,13 @@ bun run start
236
243
  * Get bueno.config.ts template
237
244
  */
238
245
  function getConfigTemplate(config: ProjectConfig): string {
239
- let dbConfig = 'undefined';
240
-
241
- if (config.database === 'sqlite') {
246
+ let dbConfig = "undefined";
247
+
248
+ if (config.database === "sqlite") {
242
249
  dbConfig = `{ url: 'sqlite:./data.db' }`;
243
- } else if (config.database === 'postgresql') {
250
+ } else if (config.database === "postgresql") {
244
251
  dbConfig = `{ url: process.env.DATABASE_URL ?? 'postgresql://localhost/${kebabCase(config.name)}' }`;
245
- } else if (config.database === 'mysql') {
252
+ } else if (config.database === "mysql") {
246
253
  dbConfig = `{ url: process.env.DATABASE_URL ?? 'mysql://localhost/${kebabCase(config.name)}' }`;
247
254
  }
248
255
 
@@ -254,7 +261,7 @@ export default defineConfig({
254
261
  host: 'localhost',
255
262
  },
256
263
 
257
- ${config.database !== 'none' ? `database: ${dbConfig},` : ''}
264
+ ${config.database !== "none" ? `database: ${dbConfig},` : ""}
258
265
 
259
266
  logger: {
260
267
  level: 'info',
@@ -276,26 +283,44 @@ export default defineConfig({
276
283
  async function createProjectFiles(
277
284
  projectPath: string,
278
285
  config: ProjectConfig,
279
- templateResult: { files: { path: string; content: string }[]; directories: string[]; dependencies?: Record<string, string>; devDependencies?: Record<string, string>; scripts?: Record<string, string> },
286
+ templateResult: {
287
+ files: { path: string; content: string }[];
288
+ directories: string[];
289
+ dependencies?: Record<string, string>;
290
+ devDependencies?: Record<string, string>;
291
+ scripts?: Record<string, string>;
292
+ },
280
293
  ): Promise<void> {
281
294
  const tasks: TaskOptions[] = [];
282
295
 
283
296
  // Create directories
284
297
  tasks.push({
285
- text: 'Creating project structure',
298
+ text: "Creating project structure",
286
299
  task: async () => {
287
300
  // Base directories
288
- await createDirectory(joinPaths(projectPath, 'server', 'modules', 'app'));
289
- await createDirectory(joinPaths(projectPath, 'server', 'common', 'middleware'));
290
- await createDirectory(joinPaths(projectPath, 'server', 'common', 'guards'));
291
- await createDirectory(joinPaths(projectPath, 'server', 'common', 'interceptors'));
292
- await createDirectory(joinPaths(projectPath, 'server', 'common', 'pipes'));
293
- await createDirectory(joinPaths(projectPath, 'server', 'common', 'filters'));
294
- await createDirectory(joinPaths(projectPath, 'server', 'database', 'migrations'));
295
- await createDirectory(joinPaths(projectPath, 'server', 'config'));
296
- await createDirectory(joinPaths(projectPath, 'tests', 'unit'));
297
- await createDirectory(joinPaths(projectPath, 'tests', 'integration'));
298
-
301
+ await createDirectory(joinPaths(projectPath, "server", "modules", "app"));
302
+ await createDirectory(
303
+ joinPaths(projectPath, "server", "common", "middleware"),
304
+ );
305
+ await createDirectory(
306
+ joinPaths(projectPath, "server", "common", "guards"),
307
+ );
308
+ await createDirectory(
309
+ joinPaths(projectPath, "server", "common", "interceptors"),
310
+ );
311
+ await createDirectory(
312
+ joinPaths(projectPath, "server", "common", "pipes"),
313
+ );
314
+ await createDirectory(
315
+ joinPaths(projectPath, "server", "common", "filters"),
316
+ );
317
+ await createDirectory(
318
+ joinPaths(projectPath, "server", "database", "migrations"),
319
+ );
320
+ await createDirectory(joinPaths(projectPath, "server", "config"));
321
+ await createDirectory(joinPaths(projectPath, "tests", "unit"));
322
+ await createDirectory(joinPaths(projectPath, "tests", "integration"));
323
+
299
324
  // Template-specific directories
300
325
  for (const dir of templateResult.directories) {
301
326
  await createDirectory(joinPaths(projectPath, dir));
@@ -305,10 +330,10 @@ async function createProjectFiles(
305
330
 
306
331
  // Create package.json
307
332
  tasks.push({
308
- text: 'Creating package.json',
333
+ text: "Creating package.json",
309
334
  task: async () => {
310
335
  await writeFile(
311
- joinPaths(projectPath, 'package.json'),
336
+ joinPaths(projectPath, "package.json"),
312
337
  getPackageJsonTemplate(config, templateResult),
313
338
  );
314
339
  },
@@ -316,10 +341,10 @@ async function createProjectFiles(
316
341
 
317
342
  // Create tsconfig.json
318
343
  tasks.push({
319
- text: 'Creating tsconfig.json',
344
+ text: "Creating tsconfig.json",
320
345
  task: async () => {
321
346
  await writeFile(
322
- joinPaths(projectPath, 'tsconfig.json'),
347
+ joinPaths(projectPath, "tsconfig.json"),
323
348
  getTsConfigTemplate(),
324
349
  );
325
350
  },
@@ -330,21 +355,18 @@ async function createProjectFiles(
330
355
  tasks.push({
331
356
  text: `Creating ${file.path}`,
332
357
  task: async () => {
333
- await writeFile(
334
- joinPaths(projectPath, file.path),
335
- file.content,
336
- );
358
+ await writeFile(joinPaths(projectPath, file.path), file.content);
337
359
  },
338
360
  });
339
361
  }
340
362
 
341
363
  // Create bueno.config.ts (not for website template)
342
- if (config.template !== 'website') {
364
+ if (config.template !== "website") {
343
365
  tasks.push({
344
- text: 'Creating bueno.config.ts',
366
+ text: "Creating bueno.config.ts",
345
367
  task: async () => {
346
368
  await writeFile(
347
- joinPaths(projectPath, 'bueno.config.ts'),
369
+ joinPaths(projectPath, "bueno.config.ts"),
348
370
  getConfigTemplate(config),
349
371
  );
350
372
  },
@@ -353,10 +375,10 @@ async function createProjectFiles(
353
375
 
354
376
  // Create .env.example
355
377
  tasks.push({
356
- text: 'Creating .env.example',
378
+ text: "Creating .env.example",
357
379
  task: async () => {
358
380
  await writeFile(
359
- joinPaths(projectPath, '.env.example'),
381
+ joinPaths(projectPath, ".env.example"),
360
382
  getEnvExampleTemplate(config),
361
383
  );
362
384
  },
@@ -364,10 +386,10 @@ async function createProjectFiles(
364
386
 
365
387
  // Create .gitignore
366
388
  tasks.push({
367
- text: 'Creating .gitignore',
389
+ text: "Creating .gitignore",
368
390
  task: async () => {
369
391
  await writeFile(
370
- joinPaths(projectPath, '.gitignore'),
392
+ joinPaths(projectPath, ".gitignore"),
371
393
  getGitignoreTemplate(),
372
394
  );
373
395
  },
@@ -375,53 +397,62 @@ async function createProjectFiles(
375
397
 
376
398
  // Create README.md
377
399
  tasks.push({
378
- text: 'Creating README.md',
400
+ text: "Creating README.md",
379
401
  task: async () => {
380
402
  await writeFile(
381
- joinPaths(projectPath, 'README.md'),
403
+ joinPaths(projectPath, "README.md"),
382
404
  getReadmeTemplate(config),
383
405
  );
384
406
  },
385
407
  });
386
408
 
387
409
  // Create Docker files if enabled
388
- if (config.docker && config.template !== 'website') {
410
+ if (config.docker && config.template !== "website") {
389
411
  tasks.push({
390
- text: 'Creating Dockerfile',
412
+ text: "Creating Dockerfile",
391
413
  task: async () => {
392
414
  await writeFile(
393
- joinPaths(projectPath, 'Dockerfile'),
394
- getDockerfileTemplate(config.name, config.database === 'none' ? undefined : config.database),
415
+ joinPaths(projectPath, "Dockerfile"),
416
+ getDockerfileTemplate(
417
+ config.name,
418
+ config.database === "none" ? undefined : config.database,
419
+ ),
395
420
  );
396
421
  },
397
422
  });
398
423
 
399
424
  tasks.push({
400
- text: 'Creating .dockerignore',
425
+ text: "Creating .dockerignore",
401
426
  task: async () => {
402
427
  await writeFile(
403
- joinPaths(projectPath, '.dockerignore'),
428
+ joinPaths(projectPath, ".dockerignore"),
404
429
  getDockerignoreTemplate(),
405
430
  );
406
431
  },
407
432
  });
408
433
 
409
434
  tasks.push({
410
- text: 'Creating docker-compose.yml',
435
+ text: "Creating docker-compose.yml",
411
436
  task: async () => {
412
437
  await writeFile(
413
- joinPaths(projectPath, 'docker-compose.yml'),
414
- getDockerComposeTemplate(config.name, config.database === 'none' ? undefined : config.database),
438
+ joinPaths(projectPath, "docker-compose.yml"),
439
+ getDockerComposeTemplate(
440
+ config.name,
441
+ config.database === "none" ? undefined : config.database,
442
+ ),
415
443
  );
416
444
  },
417
445
  });
418
446
 
419
447
  tasks.push({
420
- text: 'Creating .env.docker',
448
+ text: "Creating .env.docker",
421
449
  task: async () => {
422
450
  await writeFile(
423
- joinPaths(projectPath, '.env.docker'),
424
- getDockerEnvTemplate(config.name, config.database === 'none' ? undefined : config.database),
451
+ joinPaths(projectPath, ".env.docker"),
452
+ getDockerEnvTemplate(
453
+ config.name,
454
+ config.database === "none" ? undefined : config.database,
455
+ ),
425
456
  );
426
457
  },
427
458
  });
@@ -435,7 +466,11 @@ async function createProjectFiles(
435
466
  task: async () => {
436
467
  await writeFile(
437
468
  joinPaths(projectPath, filename),
438
- getDeployTemplate(platform, config.name, config.database === 'none' ? 'sqlite' : config.database),
469
+ getDeployTemplate(
470
+ platform,
471
+ config.name,
472
+ config.database === "none" ? "sqlite" : config.database,
473
+ ),
439
474
  );
440
475
  },
441
476
  });
@@ -446,11 +481,11 @@ async function createProjectFiles(
446
481
 
447
482
  // Import docker templates
448
483
  import {
449
- getDockerfileTemplate,
450
- getDockerignoreTemplate,
451
484
  getDockerComposeTemplate,
452
485
  getDockerEnvTemplate,
453
- } from '../templates';
486
+ getDockerfileTemplate,
487
+ getDockerignoreTemplate,
488
+ } from "../templates";
454
489
 
455
490
  /**
456
491
  * Handle new command
@@ -458,18 +493,18 @@ import {
458
493
  async function handleNew(args: ParsedArgs): Promise<void> {
459
494
  // Get project name
460
495
  let name = args.positionals[0];
461
- const useDefaults = hasFlag(args, 'yes') || hasFlag(args, 'y');
496
+ const useDefaults = hasFlag(args, "yes") || hasFlag(args, "y");
462
497
 
463
498
  // Interactive prompts if no name provided
464
499
  if (!name && isInteractive()) {
465
- name = await prompt('Project name:', {
500
+ name = await prompt("Project name:", {
466
501
  validate: validateProjectName,
467
502
  });
468
503
  }
469
504
 
470
505
  if (!name) {
471
506
  throw new CLIError(
472
- 'Project name is required. Usage: bueno new <project-name>',
507
+ "Project name is required. Usage: bueno new <project-name>",
473
508
  CLIErrorType.INVALID_ARGS,
474
509
  );
475
510
  }
@@ -480,37 +515,37 @@ async function handleNew(args: ParsedArgs): Promise<void> {
480
515
  }
481
516
 
482
517
  // Get options
483
- let template = getOption(args, 'template', {
484
- name: 'template',
485
- alias: 't',
486
- type: 'string',
487
- description: '',
518
+ let template = getOption(args, "template", {
519
+ name: "template",
520
+ alias: "t",
521
+ type: "string",
522
+ description: "",
488
523
  }) as ProjectTemplate;
489
524
 
490
- let framework = getOption(args, 'framework', {
491
- name: 'framework',
492
- alias: 'f',
493
- type: 'string',
494
- description: '',
525
+ let framework = getOption(args, "framework", {
526
+ name: "framework",
527
+ alias: "f",
528
+ type: "string",
529
+ description: "",
495
530
  }) as FrontendFramework;
496
531
 
497
- let database = getOption(args, 'database', {
498
- name: 'database',
499
- alias: 'd',
500
- type: 'string',
501
- description: '',
532
+ let database = getOption(args, "database", {
533
+ name: "database",
534
+ alias: "d",
535
+ type: "string",
536
+ description: "",
502
537
  }) as DatabaseDriver;
503
538
 
504
- const skipInstall = hasFlag(args, 'skip-install');
505
- const skipGit = hasFlag(args, 'skip-git');
506
- const docker = hasFlag(args, 'docker');
507
- const link = hasFlag(args, 'link');
508
-
539
+ const skipInstall = hasFlag(args, "skip-install");
540
+ const skipGit = hasFlag(args, "skip-git");
541
+ const docker = hasFlag(args, "docker");
542
+ const link = hasFlag(args, "link");
543
+
509
544
  // Get deployment platforms (can be specified multiple times)
510
- const deployPlatforms = getOptionValues(args, 'deploy');
511
- const validPlatforms: DeployPlatform[] = ['render', 'fly', 'railway'];
545
+ const deployPlatforms = getOptionValues(args, "deploy");
546
+ const validPlatforms: DeployPlatform[] = ["render", "fly", "railway"];
512
547
  const deploy: DeployPlatform[] = [];
513
-
548
+
514
549
  for (const platform of deployPlatforms) {
515
550
  if (validPlatforms.includes(platform as DeployPlatform)) {
516
551
  if (!deploy.includes(platform as DeployPlatform)) {
@@ -518,7 +553,7 @@ async function handleNew(args: ParsedArgs): Promise<void> {
518
553
  }
519
554
  } else {
520
555
  throw new CLIError(
521
- `Invalid deployment platform: ${platform}. Valid options are: ${validPlatforms.join(', ')}`,
556
+ `Invalid deployment platform: ${platform}. Valid options are: ${validPlatforms.join(", ")}`,
522
557
  CLIErrorType.INVALID_ARGS,
523
558
  );
524
559
  }
@@ -528,44 +563,44 @@ async function handleNew(args: ParsedArgs): Promise<void> {
528
563
  if (!useDefaults && isInteractive()) {
529
564
  if (!template) {
530
565
  template = await select<ProjectTemplate>(
531
- 'Select a template:',
566
+ "Select a template:",
532
567
  getTemplateOptions(),
533
- { default: 'default' },
568
+ { default: "default" },
534
569
  );
535
570
  }
536
571
 
537
572
  // Only ask for framework if template supports it
538
- if ((template === 'fullstack' || template === 'default') && !framework) {
573
+ if ((template === "fullstack" || template === "default") && !framework) {
539
574
  framework = await select<FrontendFramework>(
540
- 'Select a frontend framework:',
575
+ "Select a frontend framework:",
541
576
  getFrontendOptions(),
542
- { default: 'react' },
577
+ { default: "react" },
543
578
  );
544
579
  }
545
580
 
546
581
  // Website template doesn't need database or frontend selection
547
- if (template !== 'website' && !database) {
582
+ if (template !== "website" && !database) {
548
583
  database = await select<DatabaseDriver>(
549
- 'Select a database:',
584
+ "Select a database:",
550
585
  getDatabaseOptions(),
551
- { default: 'sqlite' },
586
+ { default: "sqlite" },
552
587
  );
553
588
  }
554
589
  }
555
590
 
556
591
  // Set defaults
557
- template = template || 'default';
558
- framework = framework || 'react';
559
- database = database || 'sqlite';
592
+ template = template || "default";
593
+ framework = framework || "react";
594
+ database = database || "sqlite";
560
595
 
561
596
  // For website template, override database and framework
562
- const isWebsite = template === 'website';
597
+ const isWebsite = template === "website";
563
598
 
564
599
  const config: ProjectConfig = {
565
600
  name,
566
601
  template,
567
- framework: isWebsite ? 'none' : framework,
568
- database: isWebsite ? 'none' : database,
602
+ framework: isWebsite ? "none" : framework,
603
+ database: isWebsite ? "none" : database,
569
604
  skipInstall,
570
605
  skipGit,
571
606
  docker,
@@ -586,71 +621,90 @@ async function handleNew(args: ParsedArgs): Promise<void> {
586
621
  cliConsole.header(`Creating a new Bueno project: ${colors.cyan(name)}`);
587
622
 
588
623
  const rows = [
589
- ['Template', template],
590
- ['Framework', isWebsite ? 'N/A (Static Site)' : framework],
591
- ['Database', isWebsite ? 'N/A' : database],
592
- ['Docker', docker ? colors.green('Yes') : colors.red('No')],
593
- ['Deploy', deploy.length > 0 ? colors.green(deploy.map(getDeployPlatformName).join(', ')) : colors.red('None')],
594
- ['Install dependencies', skipInstall ? colors.red('No') : colors.green('Yes')],
595
- ['Use local package', link ? colors.green('Yes (bun link)') : colors.red('No')],
624
+ ["Template", template],
625
+ ["Framework", isWebsite ? "N/A (Static Site)" : framework],
626
+ ["Database", isWebsite ? "N/A" : database],
627
+ ["Docker", docker ? colors.green("Yes") : colors.red("No")],
628
+ [
629
+ "Deploy",
630
+ deploy.length > 0
631
+ ? colors.green(deploy.map(getDeployPlatformName).join(", "))
632
+ : colors.red("None"),
633
+ ],
634
+ [
635
+ "Install dependencies",
636
+ skipInstall ? colors.red("No") : colors.green("Yes"),
637
+ ],
638
+ [
639
+ "Use local package",
640
+ link ? colors.green("Yes (bun link)") : colors.red("No"),
641
+ ],
596
642
  ];
597
643
 
598
- printTable(['Setting', 'Value'], rows);
599
- cliConsole.log('');
644
+ printTable(["Setting", "Value"], rows);
645
+ cliConsole.log("");
600
646
 
601
647
  // Get the appropriate template function
602
- const { getProjectTemplate } = await import('../templates');
648
+ const { getProjectTemplate } = await import("../templates");
603
649
  const templateFn = getProjectTemplate(template);
604
650
  const templateResult = templateFn(config);
605
651
 
606
652
  // Create project
607
- cliConsole.subheader('Creating project files...');
653
+ cliConsole.subheader("Creating project files...");
608
654
  await createProjectFiles(projectPath, config, templateResult);
609
655
 
610
656
  // Install dependencies
611
657
  if (!skipInstall) {
612
- cliConsole.subheader('Installing dependencies...');
613
- const installSpinner = spinner('Running bun install...');
658
+ cliConsole.subheader("Installing dependencies...");
659
+ const installSpinner = spinner("Running bun install...");
614
660
 
615
661
  try {
616
- const proc = Bun.spawn(['bun', 'install'], {
662
+ const proc = Bun.spawn(["bun", "install"], {
617
663
  cwd: projectPath,
618
- stdout: 'pipe',
619
- stderr: 'pipe',
664
+ stdout: "pipe",
665
+ stderr: "pipe",
620
666
  });
621
667
 
622
668
  const exitCode = await proc.exited;
623
669
 
624
670
  if (exitCode === 0) {
625
- installSpinner.success('Dependencies installed');
671
+ installSpinner.success("Dependencies installed");
626
672
  } else {
627
- installSpinner.warn('Failed to install dependencies. Run `bun install` manually.');
673
+ installSpinner.warn(
674
+ "Failed to install dependencies. Run `bun install` manually.",
675
+ );
628
676
  }
629
677
  } catch {
630
- installSpinner.warn('Failed to install dependencies. Run `bun install` manually.');
678
+ installSpinner.warn(
679
+ "Failed to install dependencies. Run `bun install` manually.",
680
+ );
631
681
  }
632
-
682
+
633
683
  // Link local @buenojs/bueno if --link flag is set
634
684
  if (link) {
635
- cliConsole.subheader('Linking local @buenojs/bueno...');
636
- const linkSpinner = spinner('Running bun link @buenojs/bueno...');
685
+ cliConsole.subheader("Linking local @buenojs/bueno...");
686
+ const linkSpinner = spinner("Running bun link @buenojs/bueno...");
637
687
 
638
688
  try {
639
- const proc = Bun.spawn(['bun', 'link', '@buenojs/bueno'], {
689
+ const proc = Bun.spawn(["bun", "link", "@buenojs/bueno"], {
640
690
  cwd: projectPath,
641
- stdout: 'pipe',
642
- stderr: 'pipe',
691
+ stdout: "pipe",
692
+ stderr: "pipe",
643
693
  });
644
694
 
645
695
  const exitCode = await proc.exited;
646
696
 
647
697
  if (exitCode === 0) {
648
- linkSpinner.success('Local @buenojs/bueno linked successfully');
698
+ linkSpinner.success("Local @buenojs/bueno linked successfully");
649
699
  } else {
650
- linkSpinner.warn('Failed to link @buenojs/bueno. Make sure you have run `bun link` in the bueno directory first.');
700
+ linkSpinner.warn(
701
+ "Failed to link @buenojs/bueno. Make sure you have run `bun link` in the bueno directory first.",
702
+ );
651
703
  }
652
704
  } catch {
653
- linkSpinner.warn('Failed to link @buenojs/bueno. Make sure you have run `bun link` in the bueno directory first.');
705
+ linkSpinner.warn(
706
+ "Failed to link @buenojs/bueno. Make sure you have run `bun link` in the bueno directory first.",
707
+ );
654
708
  }
655
709
  }
656
710
  }
@@ -659,113 +713,121 @@ async function handleNew(args: ParsedArgs): Promise<void> {
659
713
  // Users can run `git init` manually if needed
660
714
 
661
715
  // Show success message
662
- cliConsole.log('');
716
+ cliConsole.log("");
663
717
  cliConsole.success(`Project created successfully!`);
664
- cliConsole.log('');
665
- cliConsole.log('Next steps:');
718
+ cliConsole.log("");
719
+ cliConsole.log("Next steps:");
666
720
  cliConsole.log(` ${colors.cyan(`cd ${kebabCase(name)}`)}`);
667
-
721
+
668
722
  if (isWebsite) {
669
- cliConsole.log(` ${colors.cyan('bun run dev')} - Start development server`);
670
- cliConsole.log(` ${colors.cyan('bun run build')} - Build static site`);
723
+ cliConsole.log(
724
+ ` ${colors.cyan("bun run dev")} - Start development server`,
725
+ );
726
+ cliConsole.log(` ${colors.cyan("bun run build")} - Build static site`);
671
727
  } else {
672
- cliConsole.log(` ${colors.cyan('bun run dev')}`);
728
+ cliConsole.log(` ${colors.cyan("bun run dev")}`);
673
729
  }
674
-
675
- cliConsole.log('');
676
- cliConsole.log(`Documentation: ${colors.dim('https://github.com/sivaraj/bueno')}`);
730
+
731
+ cliConsole.log("");
732
+ cliConsole.log(
733
+ `Documentation: ${colors.dim("https://github.com/sivaraj/bueno")}`,
734
+ );
677
735
  }
678
736
 
679
737
  // Import the template function getter dynamically
680
738
  async function importTemplateFn(template: ProjectTemplate) {
681
- const { getProjectTemplate } = await import('../templates');
739
+ const { getProjectTemplate } = await import("../templates");
682
740
  return getProjectTemplate(template);
683
741
  }
684
742
 
685
743
  // Register the command
686
744
  defineCommand(
687
745
  {
688
- name: 'new',
689
- description: 'Create a new Bueno project',
746
+ name: "new",
747
+ description: "Create a new Bueno project",
690
748
  positionals: [
691
749
  {
692
- name: 'name',
750
+ name: "name",
693
751
  required: false,
694
- description: 'Project name',
752
+ description: "Project name",
695
753
  },
696
754
  ],
697
755
  options: [
698
756
  {
699
- name: 'template',
700
- alias: 't',
701
- type: 'string',
702
- description: 'Project template (default, minimal, fullstack, api, website)',
757
+ name: "template",
758
+ alias: "t",
759
+ type: "string",
760
+ description:
761
+ "Project template (default, minimal, fullstack, api, website)",
703
762
  },
704
763
  {
705
- name: 'framework',
706
- alias: 'f',
707
- type: 'string',
708
- description: 'Frontend framework (react, vue, svelte, solid, none)',
764
+ name: "framework",
765
+ alias: "f",
766
+ type: "string",
767
+ description: "Frontend framework (react, vue, svelte, solid, none)",
709
768
  },
710
769
  {
711
- name: 'database',
712
- alias: 'd',
713
- type: 'string',
714
- description: 'Database driver (sqlite, postgresql, mysql, none)',
770
+ name: "database",
771
+ alias: "d",
772
+ type: "string",
773
+ description: "Database driver (sqlite, postgresql, mysql, none)",
715
774
  },
716
775
  {
717
- name: 'skip-install',
718
- type: 'boolean',
776
+ name: "skip-install",
777
+ type: "boolean",
719
778
  default: false,
720
- description: 'Skip dependency installation',
779
+ description: "Skip dependency installation",
721
780
  },
722
781
  {
723
- name: 'skip-git',
724
- type: 'boolean',
782
+ name: "skip-git",
783
+ type: "boolean",
725
784
  default: false,
726
- description: 'Skip git initialization (deprecated - git init is no longer automatic)',
785
+ description:
786
+ "Skip git initialization (deprecated - git init is no longer automatic)",
727
787
  },
728
788
  {
729
- name: 'docker',
730
- type: 'boolean',
789
+ name: "docker",
790
+ type: "boolean",
731
791
  default: false,
732
- description: 'Include Docker configuration (Dockerfile, docker-compose.yml)',
792
+ description:
793
+ "Include Docker configuration (Dockerfile, docker-compose.yml)",
733
794
  },
734
795
  {
735
- name: 'link',
736
- type: 'boolean',
796
+ name: "link",
797
+ type: "boolean",
737
798
  default: false,
738
- description: 'Use local @buenojs/bueno via bun link (for development)',
799
+ description: "Use local @buenojs/bueno via bun link (for development)",
739
800
  },
740
801
  {
741
- name: 'deploy',
742
- type: 'string',
743
- description: 'Deployment platform configuration (render, fly, railway). Can be specified multiple times.',
802
+ name: "deploy",
803
+ type: "string",
804
+ description:
805
+ "Deployment platform configuration (render, fly, railway). Can be specified multiple times.",
744
806
  },
745
807
  {
746
- name: 'yes',
747
- alias: 'y',
748
- type: 'boolean',
808
+ name: "yes",
809
+ alias: "y",
810
+ type: "boolean",
749
811
  default: false,
750
- description: 'Use default options without prompts',
812
+ description: "Use default options without prompts",
751
813
  },
752
814
  ],
753
815
  examples: [
754
- 'bueno new my-app',
755
- 'bueno new my-api --template api',
756
- 'bueno new my-fullstack --template fullstack --framework react',
757
- 'bueno new my-project --database postgresql',
758
- 'bueno new my-website --template website',
759
- 'bueno new my-app --docker',
760
- 'bueno new my-app --docker --database postgresql',
761
- 'bueno new my-app --deploy render',
762
- 'bueno new my-app --deploy fly',
763
- 'bueno new my-app --deploy render --deploy fly',
764
- 'bueno new my-app --docker --deploy render',
765
- 'bueno new my-app --docker --database postgresql --deploy render',
766
- 'bueno new my-app -y',
767
- 'bueno new my-app --link',
816
+ "bueno new my-app",
817
+ "bueno new my-api --template api",
818
+ "bueno new my-fullstack --template fullstack --framework react",
819
+ "bueno new my-project --database postgresql",
820
+ "bueno new my-website --template website",
821
+ "bueno new my-app --docker",
822
+ "bueno new my-app --docker --database postgresql",
823
+ "bueno new my-app --deploy render",
824
+ "bueno new my-app --deploy fly",
825
+ "bueno new my-app --deploy render --deploy fly",
826
+ "bueno new my-app --docker --deploy render",
827
+ "bueno new my-app --docker --database postgresql --deploy render",
828
+ "bueno new my-app -y",
829
+ "bueno new my-app --link",
768
830
  ],
769
831
  },
770
832
  handleNew,
771
- );
833
+ );