create-oven 0.1.0 → 0.2.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 (2) hide show
  1. package/index.js +720 -360
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  /**
4
4
  * create-oven - Create a new Oven project
5
+ * Like create-next-app but for Bun
5
6
  *
6
7
  * Usage:
7
8
  * npx create-oven my-app
@@ -10,199 +11,659 @@
10
11
 
11
12
  import fs from 'fs';
12
13
  import path from 'path';
14
+ import readline from 'readline';
13
15
  import { fileURLToPath } from 'url';
16
+ import { execSync } from 'child_process';
14
17
 
15
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
19
 
17
- const COLORS = {
20
+ // Colors for terminal output
21
+ const c = {
18
22
  reset: '\x1b[0m',
19
- bright: '\x1b[1m',
23
+ bold: '\x1b[1m',
24
+ dim: '\x1b[2m',
20
25
  red: '\x1b[31m',
21
26
  green: '\x1b[32m',
22
27
  yellow: '\x1b[33m',
23
28
  blue: '\x1b[34m',
29
+ magenta: '\x1b[35m',
24
30
  cyan: '\x1b[36m',
31
+ white: '\x1b[37m',
32
+ bgBlue: '\x1b[44m',
25
33
  };
26
34
 
27
- function log(msg) {
28
- console.log(msg);
35
+ // Create readline interface for prompts
36
+ function createPrompt() {
37
+ return readline.createInterface({
38
+ input: process.stdin,
39
+ output: process.stdout,
40
+ });
29
41
  }
30
42
 
31
- function success(msg) {
32
- console.log(`${COLORS.green}${msg}${COLORS.reset}`);
43
+ // Ask a yes/no question
44
+ async function askYesNo(rl, question, defaultValue = true) {
45
+ const defaultText = defaultValue ? 'Yes' : 'No';
46
+ const hint = defaultValue ? '(Y/n)' : '(y/N)';
47
+
48
+ return new Promise((resolve) => {
49
+ rl.question(`${c.cyan}?${c.reset} ${question} ${c.dim}${hint}${c.reset} `, (answer) => {
50
+ if (!answer.trim()) {
51
+ resolve(defaultValue);
52
+ } else {
53
+ resolve(answer.toLowerCase().startsWith('y'));
54
+ }
55
+ });
56
+ });
33
57
  }
34
58
 
35
- function error(msg) {
36
- console.error(`${COLORS.red}${msg}${COLORS.reset}`);
59
+ // Ask for text input
60
+ async function askText(rl, question, defaultValue = '') {
61
+ const hint = defaultValue ? ` ${c.dim}(${defaultValue})${c.reset}` : '';
62
+
63
+ return new Promise((resolve) => {
64
+ rl.question(`${c.cyan}?${c.reset} ${question}${hint} `, (answer) => {
65
+ resolve(answer.trim() || defaultValue);
66
+ });
67
+ });
37
68
  }
38
69
 
39
- function info(msg) {
40
- console.log(`${COLORS.cyan}${msg}${COLORS.reset}`);
70
+ // Display a spinner
71
+ function spinner(text) {
72
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
73
+ let i = 0;
74
+ const interval = setInterval(() => {
75
+ process.stdout.write(`\r${c.cyan}${frames[i]}${c.reset} ${text}`);
76
+ i = (i + 1) % frames.length;
77
+ }, 80);
78
+
79
+ return {
80
+ stop: (finalText) => {
81
+ clearInterval(interval);
82
+ process.stdout.write(`\r${c.green}✓${c.reset} ${finalText || text}\n`);
83
+ },
84
+ fail: (finalText) => {
85
+ clearInterval(interval);
86
+ process.stdout.write(`\r${c.red}✗${c.reset} ${finalText || text}\n`);
87
+ },
88
+ };
41
89
  }
42
90
 
91
+ // Main function
43
92
  async function main() {
44
93
  const args = process.argv.slice(2);
45
- const projectName = args[0];
46
-
47
- if (!projectName || projectName === '--help' || projectName === '-h') {
48
- log(`
49
- 🔥 ${COLORS.bright}create-oven${COLORS.reset} - Create a new Oven project
50
-
51
- ${COLORS.yellow}Usage:${COLORS.reset}
52
- npx create-oven <project-name>
53
- bunx create-oven <project-name>
54
94
 
55
- ${COLORS.yellow}Examples:${COLORS.reset}
95
+ // Show help
96
+ if (args.includes('--help') || args.includes('-h')) {
97
+ console.log(`
98
+ ${c.bold}${c.cyan}create-oven${c.reset} - Create a new Oven project
99
+
100
+ ${c.yellow}Usage:${c.reset}
101
+ npx create-oven [project-name] [options]
102
+ bunx create-oven [project-name] [options]
103
+
104
+ ${c.yellow}Options:${c.reset}
105
+ --typescript, --ts Use TypeScript (default: true)
106
+ --tailwind Add Tailwind CSS
107
+ --eslint Add ESLint
108
+ --src-dir Use src/ directory
109
+ --app Use App Router (default: true)
110
+ --import-alias <alias> Set import alias (default: @/*)
111
+ --use-bun Use Bun for installing dependencies
112
+ --use-npm Use npm for installing dependencies
113
+ -h, --help Show this help message
114
+ -v, --version Show version
115
+
116
+ ${c.yellow}Examples:${c.reset}
56
117
  npx create-oven my-app
57
- bunx create-oven my-awesome-app
118
+ npx create-oven my-app --tailwind --eslint
119
+ bunx create-oven my-app --src-dir
58
120
 
59
- ${COLORS.yellow}Options:${COLORS.reset}
60
- -h, --help Show this help message
61
- -v, --version Show version
62
-
63
- ${COLORS.cyan}Learn more: https://github.com/oven-ttta/oven-framework${COLORS.reset}
121
+ ${c.cyan}Learn more: https://github.com/oven-ttta/oven-framework${c.reset}
64
122
  `);
65
- process.exit(projectName ? 0 : 1);
123
+ process.exit(0);
66
124
  }
67
125
 
68
- if (projectName === '--version' || projectName === '-v') {
69
- log('create-oven v0.1.0');
126
+ // Show version
127
+ if (args.includes('--version') || args.includes('-v')) {
128
+ console.log('create-oven v0.2.0');
70
129
  process.exit(0);
71
130
  }
72
131
 
73
- log(`
74
- 🔥 Creating new Oven project: ${COLORS.bright}${projectName}${COLORS.reset}
75
- `);
132
+ console.log(`
133
+ ${c.bold}${c.cyan} ╔═══════════════════════════════════════╗
134
+ ║ ║
135
+ ║ 🔥 Create Oven App v0.2.0 ║
136
+ ║ ║
137
+ ║ Next.js-style framework for Bun ║
138
+ ║ ║
139
+ ╚═══════════════════════════════════════╝${c.reset}
140
+ `);
141
+
142
+ const rl = createPrompt();
143
+
144
+ try {
145
+ // Get project name
146
+ let projectName = args.find(arg => !arg.startsWith('-'));
147
+
148
+ if (!projectName) {
149
+ projectName = await askText(rl, 'What is your project named?', 'my-app');
150
+ }
151
+
152
+ const projectDir = path.join(process.cwd(), projectName);
153
+
154
+ // Check if directory exists
155
+ if (fs.existsSync(projectDir)) {
156
+ console.log(`\n${c.red}✗${c.reset} Directory "${projectName}" already exists!`);
157
+ rl.close();
158
+ process.exit(1);
159
+ }
160
+
161
+ // Parse CLI flags
162
+ const hasFlag = (flag) => args.includes(flag);
163
+
164
+ // Interactive prompts (like create-next-app)
165
+ console.log();
166
+
167
+ const useTypescript = hasFlag('--typescript') || hasFlag('--ts') ||
168
+ await askYesNo(rl, 'Would you like to use TypeScript?', true);
169
+
170
+ const useEslint = hasFlag('--eslint') ||
171
+ await askYesNo(rl, 'Would you like to use ESLint?', true);
172
+
173
+ const useTailwind = hasFlag('--tailwind') ||
174
+ await askYesNo(rl, 'Would you like to use Tailwind CSS?', true);
175
+
176
+ const useSrcDir = hasFlag('--src-dir') ||
177
+ await askYesNo(rl, 'Would you like to use `src/` directory?', false);
178
+
179
+ const useAppRouter = hasFlag('--app') ||
180
+ await askYesNo(rl, 'Would you like to use App Router? (recommended)', true);
181
+
182
+ const importAliasArg = args.find((arg, i) => args[i - 1] === '--import-alias');
183
+ const importAlias = importAliasArg ||
184
+ await askText(rl, 'What import alias would you like configured?', '@/*');
185
+
186
+ rl.close();
187
+
188
+ console.log();
189
+
190
+ // Create project
191
+ const spin = spinner('Creating project structure...');
192
+
193
+ // Determine base directory
194
+ const baseDir = useSrcDir ? path.join(projectDir, 'src') : projectDir;
195
+ const appDir = path.join(baseDir, 'app');
196
+
197
+ // Create directories
198
+ const dirs = [
199
+ appDir,
200
+ path.join(appDir, 'api', 'hello'),
201
+ path.join(appDir, 'about'),
202
+ path.join(projectDir, 'public'),
203
+ ];
204
+
205
+ for (const dir of dirs) {
206
+ fs.mkdirSync(dir, { recursive: true });
207
+ }
208
+
209
+ spin.stop('Created project structure');
210
+
211
+ // Create files
212
+ const spin2 = spinner('Creating configuration files...');
213
+
214
+ // package.json
215
+ const ext = useTypescript ? 'ts' : 'js';
216
+ const pkg = {
217
+ name: projectName,
218
+ version: '0.1.0',
219
+ private: true,
220
+ scripts: {
221
+ dev: `bun run --hot ${useSrcDir ? 'src/' : ''}server.${ext}`,
222
+ build: `bun build ./${useSrcDir ? 'src/' : ''}server.${ext} --outdir ./dist --target bun`,
223
+ start: 'bun run dist/server.js',
224
+ lint: useEslint ? 'eslint . --ext .ts,.tsx' : undefined,
225
+ },
226
+ dependencies: {},
227
+ devDependencies: {
228
+ ...(useTypescript && { '@types/bun': 'latest', 'typescript': '^5.3.0' }),
229
+ ...(useEslint && { 'eslint': '^8.0.0', '@typescript-eslint/eslint-plugin': '^6.0.0', '@typescript-eslint/parser': '^6.0.0' }),
230
+ ...(useTailwind && { 'tailwindcss': '^3.4.0', 'postcss': '^8.4.0', 'autoprefixer': '^10.4.0' }),
231
+ },
232
+ };
233
+ // Remove undefined scripts
234
+ Object.keys(pkg.scripts).forEach(key => {
235
+ if (pkg.scripts[key] === undefined) delete pkg.scripts[key];
236
+ });
237
+ fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(pkg, null, 2));
238
+
239
+ // tsconfig.json
240
+ if (useTypescript) {
241
+ const tsconfig = {
242
+ compilerOptions: {
243
+ target: 'ES2017',
244
+ lib: ['dom', 'dom.iterable', 'esnext'],
245
+ allowJs: true,
246
+ skipLibCheck: true,
247
+ strict: true,
248
+ noEmit: true,
249
+ esModuleInterop: true,
250
+ module: 'esnext',
251
+ moduleResolution: 'bundler',
252
+ resolveJsonModule: true,
253
+ isolatedModules: true,
254
+ jsx: 'preserve',
255
+ incremental: true,
256
+ types: ['bun-types'],
257
+ paths: {
258
+ [importAlias]: [useSrcDir ? './src/*' : './*'],
259
+ },
260
+ baseUrl: '.',
261
+ },
262
+ include: [useSrcDir ? 'src/**/*.ts' : '**/*.ts', useSrcDir ? 'src/**/*.tsx' : '**/*.tsx'],
263
+ exclude: ['node_modules', 'dist'],
264
+ };
265
+ fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
266
+ }
267
+
268
+ // ESLint config
269
+ if (useEslint) {
270
+ const eslintConfig = {
271
+ extends: [
272
+ 'eslint:recommended',
273
+ ...(useTypescript ? ['plugin:@typescript-eslint/recommended'] : []),
274
+ ],
275
+ parser: useTypescript ? '@typescript-eslint/parser' : undefined,
276
+ plugins: useTypescript ? ['@typescript-eslint'] : [],
277
+ parserOptions: {
278
+ ecmaVersion: 'latest',
279
+ sourceType: 'module',
280
+ },
281
+ rules: {},
282
+ ignorePatterns: ['node_modules/', 'dist/'],
283
+ };
284
+ fs.writeFileSync(path.join(projectDir, '.eslintrc.json'), JSON.stringify(eslintConfig, null, 2));
285
+ }
286
+
287
+ // Tailwind config
288
+ if (useTailwind) {
289
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
290
+ module.exports = {
291
+ content: [
292
+ './${useSrcDir ? 'src/' : ''}app/**/*.{js,ts,jsx,tsx,mdx}',
293
+ './${useSrcDir ? 'src/' : ''}components/**/*.{js,ts,jsx,tsx,mdx}',
294
+ ],
295
+ theme: {
296
+ extend: {
297
+ colors: {
298
+ oven: {
299
+ 50: '#fff5f2',
300
+ 100: '#ffe6de',
301
+ 200: '#ffc9b8',
302
+ 300: '#ffa088',
303
+ 400: '#ff6b35',
304
+ 500: '#f7531e',
305
+ 600: '#e63d0a',
306
+ 700: '#bf3009',
307
+ 800: '#99290d',
308
+ 900: '#7d2510',
309
+ },
310
+ },
311
+ },
312
+ },
313
+ plugins: [],
314
+ };
315
+ `;
316
+ fs.writeFileSync(path.join(projectDir, 'tailwind.config.js'), tailwindConfig);
76
317
 
77
- const projectDir = path.join(process.cwd(), projectName);
318
+ const postcssConfig = `module.exports = {
319
+ plugins: {
320
+ tailwindcss: {},
321
+ autoprefixer: {},
322
+ },
323
+ };
324
+ `;
325
+ fs.writeFileSync(path.join(projectDir, 'postcss.config.js'), postcssConfig);
326
+ }
78
327
 
79
- // Check if directory exists
80
- if (fs.existsSync(projectDir)) {
81
- error(` Directory "${projectName}" already exists!`);
82
- process.exit(1);
83
- }
328
+ // oven.config.ts
329
+ const ovenConfig = `${useTypescript ? "import type { OvenConfig } from './app/types';\n\n" : ''}const config${useTypescript ? ': OvenConfig' : ''} = {
330
+ // Server configuration
331
+ port: 3000,
84
332
 
85
- // Create project directory
86
- fs.mkdirSync(projectDir, { recursive: true });
87
-
88
- // Create project structure
89
- const dirs = [
90
- 'app',
91
- 'app/api/hello',
92
- 'app/about',
93
- 'public',
94
- 'components',
95
- 'lib',
96
- ];
97
-
98
- for (const dir of dirs) {
99
- fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
100
- }
333
+ // Directories
334
+ appDir: '${useSrcDir ? 'src/' : ''}app',
335
+ publicDir: 'public',
336
+ };
101
337
 
102
- info(' Creating project files...');
103
-
104
- // package.json
105
- const pkg = {
106
- name: projectName,
107
- version: '0.1.0',
108
- type: 'module',
109
- scripts: {
110
- dev: 'bun run --hot server.ts',
111
- build: 'bun build ./server.ts --outdir ./dist --target bun',
112
- start: 'bun run dist/server.js',
113
- },
114
- dependencies: {},
115
- devDependencies: {
116
- '@types/bun': 'latest',
117
- typescript: '^5.3.0',
118
- },
119
- };
120
- fs.writeFileSync(
121
- path.join(projectDir, 'package.json'),
122
- JSON.stringify(pkg, null, 2)
123
- );
124
-
125
- // tsconfig.json
126
- const tsconfig = {
127
- compilerOptions: {
128
- target: 'ESNext',
129
- module: 'ESNext',
130
- moduleResolution: 'bundler',
131
- strict: true,
132
- esModuleInterop: true,
133
- skipLibCheck: true,
134
- forceConsistentCasingInFileNames: true,
135
- jsx: 'preserve',
136
- lib: ['ESNext', 'DOM'],
137
- types: ['bun-types'],
138
- },
139
- include: ['**/*.ts', '**/*.tsx'],
140
- exclude: ['node_modules'],
338
+ export default config;
339
+ `;
340
+ fs.writeFileSync(path.join(projectDir, `oven.config.${ext}`), ovenConfig);
341
+
342
+ spin2.stop('Created configuration files');
343
+
344
+ // Create app files
345
+ const spin3 = spinner('Creating app files...');
346
+
347
+ // Types
348
+ const typesContent = `export interface PageProps {
349
+ params: Record<string, string>;
350
+ searchParams: Record<string, string | string[] | undefined>;
351
+ }
352
+
353
+ export interface LayoutProps {
354
+ children: ${useTypescript ? 'string' : 'any'};
355
+ params: Record<string, string>;
356
+ }
357
+
358
+ export interface Metadata {
359
+ title?: string | { default: string; template?: string };
360
+ description?: string;
361
+ keywords?: string[];
362
+ openGraph?: {
363
+ title?: string;
364
+ description?: string;
365
+ images?: { url: string }[];
141
366
  };
142
- fs.writeFileSync(
143
- path.join(projectDir, 'tsconfig.json'),
144
- JSON.stringify(tsconfig, null, 2)
145
- );
367
+ }
368
+
369
+ export interface OvenConfig {
370
+ port?: number;
371
+ appDir?: string;
372
+ publicDir?: string;
373
+ }
374
+ `;
375
+ fs.writeFileSync(path.join(appDir, `types.${ext}`), typesContent);
376
+
377
+ // globals.css
378
+ const globalsCss = useTailwind ? `@tailwind base;
379
+ @tailwind components;
380
+ @tailwind utilities;
381
+
382
+ :root {
383
+ --foreground-rgb: 0, 0, 0;
384
+ --background-rgb: 255, 255, 255;
385
+ }
386
+
387
+ @media (prefers-color-scheme: dark) {
388
+ :root {
389
+ --foreground-rgb: 255, 255, 255;
390
+ --background-rgb: 10, 10, 10;
391
+ }
392
+ }
393
+
394
+ body {
395
+ color: rgb(var(--foreground-rgb));
396
+ background: rgb(var(--background-rgb));
397
+ }
398
+ ` : `* {
399
+ margin: 0;
400
+ padding: 0;
401
+ box-sizing: border-box;
402
+ }
403
+
404
+ :root {
405
+ --foreground-rgb: 0, 0, 0;
406
+ --background-rgb: 255, 255, 255;
407
+ --oven-primary: #ff6b35;
408
+ --oven-secondary: #f7931e;
409
+ }
410
+
411
+ body {
412
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
413
+ color: rgb(var(--foreground-rgb));
414
+ background: rgb(var(--background-rgb));
415
+ }
416
+
417
+ a {
418
+ color: inherit;
419
+ text-decoration: none;
420
+ }
421
+ `;
422
+ fs.writeFileSync(path.join(appDir, 'globals.css'), globalsCss);
423
+
424
+ // favicon.ico (placeholder - just create the public dir marker)
425
+ fs.writeFileSync(path.join(projectDir, 'public', '.gitkeep'), '');
426
+
427
+ // Root Layout
428
+ const layoutContent = `import${useTypescript ? ' type' : ''} { LayoutProps } from './types';
429
+ import './globals.css';
430
+
431
+ export const metadata = {
432
+ title: {
433
+ default: '${projectName}',
434
+ template: '%s | ${projectName}',
435
+ },
436
+ description: 'Built with Oven - A Next.js-style framework for Bun',
437
+ };
438
+
439
+ export default function RootLayout({ children }${useTypescript ? ': LayoutProps' : ''}) {
440
+ return \`
441
+ <!DOCTYPE html>
442
+ <html lang="en">
443
+ <head>
444
+ <meta charset="UTF-8">
445
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
446
+ <link rel="icon" href="/favicon.ico" />
447
+ </head>
448
+ <body${useTailwind ? ' class="antialiased"' : ''}>
449
+ \${children}
450
+ </body>
451
+ </html>
452
+ \`;
453
+ }
454
+ `;
455
+ fs.writeFileSync(path.join(appDir, `layout.${ext}x`), layoutContent);
456
+
457
+ // Home Page
458
+ const homePageContent = `import${useTypescript ? ' type' : ''} { PageProps, Metadata } from './types';
459
+
460
+ export const metadata${useTypescript ? ': Metadata' : ''} = {
461
+ title: 'Home',
462
+ description: 'Welcome to ${projectName}',
463
+ };
464
+
465
+ export default function HomePage({ searchParams }${useTypescript ? ': PageProps' : ''}) {
466
+ return \`
467
+ <main ${useTailwind ? 'class="flex min-h-screen flex-col items-center justify-center p-24"' : 'style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; padding: 6rem;"'}>
468
+ <div ${useTailwind ? 'class="text-center"' : 'style="text-align: center;"'}>
469
+ <h1 ${useTailwind ? 'class="text-6xl font-bold mb-4 bg-gradient-to-r from-oven-400 to-oven-500 bg-clip-text text-transparent"' : 'style="font-size: 3.5rem; font-weight: bold; margin-bottom: 1rem; background: linear-gradient(135deg, #ff6b35, #f7931e); -webkit-background-clip: text; -webkit-text-fill-color: transparent;"'}>
470
+ Welcome to ${projectName}
471
+ </h1>
472
+
473
+ <p ${useTailwind ? 'class="text-xl text-gray-600 mb-8"' : 'style="font-size: 1.25rem; color: #666; margin-bottom: 2rem;"'}>
474
+ Get started by editing
475
+ <code ${useTailwind ? 'class="px-2 py-1 bg-gray-100 rounded text-oven-400 font-mono"' : 'style="padding: 0.25rem 0.5rem; background: #f5f5f5; border-radius: 4px; color: #ff6b35; font-family: monospace;"'}>
476
+ ${useSrcDir ? 'src/' : ''}app/page.${ext}x
477
+ </code>
478
+ </p>
479
+
480
+ <div ${useTailwind ? 'class="flex gap-4 flex-wrap justify-center"' : 'style="display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center;"'}>
481
+ <a href="/about" ${useTailwind ? 'class="px-6 py-3 bg-gradient-to-r from-oven-400 to-oven-500 text-white rounded-lg font-medium hover:opacity-90 transition"' : 'style="padding: 0.75rem 1.5rem; background: linear-gradient(135deg, #ff6b35, #f7931e); color: white; border-radius: 8px; font-weight: 500;"'}>
482
+ Learn More →
483
+ </a>
484
+ <a href="/api/hello" ${useTailwind ? 'class="px-6 py-3 bg-gray-900 text-white rounded-lg font-medium hover:bg-gray-800 transition"' : 'style="padding: 0.75rem 1.5rem; background: #1a1a1a; color: white; border-radius: 8px; font-weight: 500;"'}>
485
+ API Example
486
+ </a>
487
+ <a href="https://github.com/oven-ttta/oven-framework" target="_blank" ${useTailwind ? 'class="px-6 py-3 border border-gray-300 rounded-lg font-medium hover:border-gray-400 transition"' : 'style="padding: 0.75rem 1.5rem; border: 1px solid #ddd; border-radius: 8px; font-weight: 500;"'}>
488
+ GitHub ⭐
489
+ </a>
490
+ </div>
491
+ </div>
492
+
493
+ <div ${useTailwind ? 'class="mt-16 grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl"' : 'style="margin-top: 4rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; max-width: 900px;"'}>
494
+ <div ${useTailwind ? 'class="p-6 bg-white border border-gray-200 rounded-xl hover:border-oven-400 transition"' : 'style="padding: 1.5rem; background: white; border: 1px solid #eee; border-radius: 12px;"'}>
495
+ <h3 ${useTailwind ? 'class="text-lg font-semibold mb-2"' : 'style="font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem;"'}>📁 File-based Routing</h3>
496
+ <p ${useTailwind ? 'class="text-gray-600 text-sm"' : 'style="color: #666; font-size: 0.9rem;"'}>
497
+ Create routes by adding files to the app directory. Just like Next.js!
498
+ </p>
499
+ </div>
500
+
501
+ <div ${useTailwind ? 'class="p-6 bg-white border border-gray-200 rounded-xl hover:border-oven-400 transition"' : 'style="padding: 1.5rem; background: white; border: 1px solid #eee; border-radius: 12px;"'}>
502
+ <h3 ${useTailwind ? 'class="text-lg font-semibold mb-2"' : 'style="font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem;"'}>⚡ Blazing Fast</h3>
503
+ <p ${useTailwind ? 'class="text-gray-600 text-sm"' : 'style="color: #666; font-size: 0.9rem;"'}>
504
+ Powered by Bun runtime. Up to 4x faster than Node.js.
505
+ </p>
506
+ </div>
507
+
508
+ <div ${useTailwind ? 'class="p-6 bg-white border border-gray-200 rounded-xl hover:border-oven-400 transition"' : 'style="padding: 1.5rem; background: white; border: 1px solid #eee; border-radius: 12px;"'}>
509
+ <h3 ${useTailwind ? 'class="text-lg font-semibold mb-2"' : 'style="font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem;"'}>🔌 API Routes</h3>
510
+ <p ${useTailwind ? 'class="text-gray-600 text-sm"' : 'style="color: #666; font-size: 0.9rem;"'}>
511
+ Build APIs with route handlers. Support GET, POST, PUT, DELETE.
512
+ </p>
513
+ </div>
514
+ </div>
515
+ </main>
516
+ \`;
517
+ }
518
+ `;
519
+ fs.writeFileSync(path.join(appDir, `page.${ext}x`), homePageContent);
520
+
521
+ // About Page
522
+ const aboutPageContent = `import${useTypescript ? ' type' : ''} { PageProps, Metadata } from '../types';
523
+
524
+ export const metadata${useTypescript ? ': Metadata' : ''} = {
525
+ title: 'About',
526
+ description: 'About ${projectName}',
527
+ };
528
+
529
+ export default function AboutPage({ params }${useTypescript ? ': PageProps' : ''}) {
530
+ return \`
531
+ <main ${useTailwind ? 'class="max-w-4xl mx-auto px-6 py-16"' : 'style="max-width: 900px; margin: 0 auto; padding: 4rem 1.5rem;"'}>
532
+ <a href="/" ${useTailwind ? 'class="text-oven-400 hover:underline mb-8 inline-block"' : 'style="color: #ff6b35; margin-bottom: 2rem; display: inline-block;"'}>
533
+ ← Back to Home
534
+ </a>
535
+
536
+ <h1 ${useTailwind ? 'class="text-4xl font-bold mb-6"' : 'style="font-size: 2.5rem; font-weight: bold; margin-bottom: 1.5rem;"'}>
537
+ About ${projectName}
538
+ </h1>
539
+
540
+ <p ${useTailwind ? 'class="text-lg text-gray-600 mb-8 leading-relaxed"' : 'style="font-size: 1.1rem; color: #666; margin-bottom: 2rem; line-height: 1.8;"'}>
541
+ This project was bootstrapped with <strong>create-oven</strong>,
542
+ a Next.js-style framework powered by the Bun runtime.
543
+ </p>
544
+
545
+ <div ${useTailwind ? 'class="bg-oven-50 border-l-4 border-oven-400 p-6 rounded-r-lg mb-8"' : 'style="background: #fff5f2; border-left: 4px solid #ff6b35; padding: 1.5rem; border-radius: 0 8px 8px 0; margin-bottom: 2rem;"'}>
546
+ <h2 ${useTailwind ? 'class="text-xl font-semibold mb-4"' : 'style="font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem;"'}>
547
+ 🚀 Why Oven?
548
+ </h2>
549
+ <ul ${useTailwind ? 'class="space-y-2 text-gray-700"' : 'style="list-style: none;"'}>
550
+ <li ${useTailwind ? '' : 'style="margin-bottom: 0.5rem; color: #444;"'}>✓ Built for Bun runtime - 4x faster than Node.js</li>
551
+ <li ${useTailwind ? '' : 'style="margin-bottom: 0.5rem; color: #444;"'}>✓ Next.js-style file-based routing</li>
552
+ <li ${useTailwind ? '' : 'style="margin-bottom: 0.5rem; color: #444;"'}>✓ Zero configuration needed</li>
553
+ <li ${useTailwind ? '' : 'style="margin-bottom: 0.5rem; color: #444;"'}>✓ Full TypeScript support</li>
554
+ ${useTailwind ? "<li>✓ Tailwind CSS integration</li>" : ""}
555
+ </ul>
556
+ </div>
146
557
 
147
- // server.ts - Main server file
148
- const serverFile = `/**
558
+ <h2 ${useTailwind ? 'class="text-2xl font-semibold mb-4"' : 'style="font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem;"'}>
559
+ Learn More
560
+ </h2>
561
+
562
+ <div ${useTailwind ? 'class="flex gap-4 flex-wrap"' : 'style="display: flex; gap: 1rem; flex-wrap: wrap;"'}>
563
+ <a href="https://github.com/oven-ttta/oven-framework" target="_blank"
564
+ ${useTailwind ? 'class="px-4 py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 transition"' : 'style="padding: 0.5rem 1rem; background: #1a1a1a; color: white; border-radius: 8px;"'}>
565
+ GitHub
566
+ </a>
567
+ <a href="https://bun.sh/docs" target="_blank"
568
+ ${useTailwind ? 'class="px-4 py-2 border border-gray-300 rounded-lg hover:border-gray-400 transition"' : 'style="padding: 0.5rem 1rem; border: 1px solid #ddd; border-radius: 8px;"'}>
569
+ Bun Docs
570
+ </a>
571
+ </div>
572
+ </main>
573
+ \`;
574
+ }
575
+ `;
576
+ fs.writeFileSync(path.join(appDir, 'about', `page.${ext}x`), aboutPageContent);
577
+
578
+ // API Route
579
+ const apiRouteContent = `// API Route: GET /api/hello
580
+
581
+ export async function GET(request${useTypescript ? ': Request' : ''}) {
582
+ return Response.json({
583
+ message: 'Hello from ${projectName}!',
584
+ timestamp: new Date().toISOString(),
585
+ framework: 'Oven 🔥',
586
+ runtime: 'Bun',
587
+ });
588
+ }
589
+
590
+ export async function POST(request${useTypescript ? ': Request' : ''}) {
591
+ const body = await request.json().catch(() => ({}));
592
+
593
+ return Response.json({
594
+ message: 'Data received!',
595
+ data: body,
596
+ timestamp: new Date().toISOString(),
597
+ }, { status: 201 });
598
+ }
599
+ `;
600
+ fs.writeFileSync(path.join(appDir, 'api', 'hello', `route.${ext}`), apiRouteContent);
601
+
602
+ // Server file
603
+ const serverContent = `/**
149
604
  * Oven Server - ${projectName}
150
605
  * A Next.js-style framework powered by Bun
151
606
  */
152
607
 
153
608
  const PORT = parseInt(process.env.PORT || '3000');
154
609
 
155
- // Simple router
156
- const routes: Map<string, (req: Request) => Promise<Response>> = new Map();
610
+ // Routes map
611
+ const routes${useTypescript ? ': Map<string, (req: Request) => Promise<Response>>' : ''} = new Map();
157
612
 
158
- // Scan app directory for routes
613
+ // Scan and register routes
159
614
  async function scanRoutes() {
160
- const glob = new Bun.Glob('**/page.tsx');
161
- for await (const file of glob.scan({ cwd: './app' })) {
162
- const routePath = '/' + file.replace(/\\/page\\.tsx$/, '').replace(/^page\\.tsx$/, '');
163
- const normalizedPath = routePath === '/' ? '/' : routePath;
615
+ const appDir = './${useSrcDir ? 'src/' : ''}app';
164
616
 
165
- routes.set(normalizedPath, async (req: Request) => {
166
- const module = await import(\`./app/\${file}\`);
167
- const content = await module.default({ params: {}, searchParams: {} });
617
+ // Scan pages
618
+ const pageGlob = new Bun.Glob('**/page.{ts,tsx,js,jsx}');
619
+ for await (const file of pageGlob.scan({ cwd: appDir })) {
620
+ const routePath = '/' + file
621
+ .replace(/\\/page\\.(ts|tsx|js|jsx)$/, '')
622
+ .replace(/^page\\.(ts|tsx|js|jsx)$/, '')
623
+ .replace(/\\/$/, '') || '/';
168
624
 
169
- // Wrap with layout
170
- let html = content;
625
+ const normalizedPath = routePath === '' ? '/' : routePath;
626
+
627
+ routes.set(normalizedPath, async (req${useTypescript ? ': Request' : ''}) => {
171
628
  try {
172
- const layoutModule = await import('./app/layout.tsx');
173
- html = await layoutModule.default({ children: content, params: {} });
174
- } catch {}
175
-
176
- // Generate metadata
177
- const metadata = module.metadata || {};
178
- const title = typeof metadata.title === 'string' ? metadata.title : metadata.title?.default || '${projectName}';
179
-
180
- const fullHtml = \`<!DOCTYPE html>
181
- <html lang="en">
182
- <head>
183
- <meta charset="UTF-8">
184
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
185
- <title>\${title}</title>
186
- \${metadata.description ? \`<meta name="description" content="\${metadata.description}">\` : ''}
187
- </head>
188
- <body>
189
- \${html}
190
- </body>
191
- </html>\`;
192
-
193
- return new Response(fullHtml, {
194
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
195
- });
629
+ const module = await import(\`\${appDir}/\${file}\`);
630
+ const content = await module.default({ params: {}, searchParams: {} });
631
+
632
+ // Get layout
633
+ let html = content;
634
+ try {
635
+ const layoutModule = await import(\`\${appDir}/layout.tsx\`);
636
+ html = await layoutModule.default({ children: content, params: {} });
637
+ } catch {}
638
+
639
+ // Metadata
640
+ const metadata = module.metadata || {};
641
+ const title = typeof metadata.title === 'string'
642
+ ? metadata.title
643
+ : metadata.title?.default || '${projectName}';
644
+
645
+ return new Response(html, {
646
+ headers: {
647
+ 'Content-Type': 'text/html; charset=utf-8',
648
+ },
649
+ });
650
+ } catch (error) {
651
+ console.error('Page error:', error);
652
+ return new Response('Internal Server Error', { status: 500 });
653
+ }
196
654
  });
197
655
  }
198
656
 
199
657
  // Scan API routes
200
- const apiGlob = new Bun.Glob('**/route.ts');
201
- for await (const file of apiGlob.scan({ cwd: './app' })) {
202
- const routePath = '/' + file.replace(/\\/route\\.ts$/, '').replace(/^route\\.ts$/, '');
203
-
204
- routes.set(routePath, async (req: Request) => {
205
- const module = await import(\`./app/\${file}\`);
658
+ const apiGlob = new Bun.Glob('**/route.{ts,js}');
659
+ for await (const file of apiGlob.scan({ cwd: appDir })) {
660
+ const routePath = '/' + file
661
+ .replace(/\\/route\\.(ts|js)$/, '')
662
+ .replace(/^route\\.(ts|js)$/, '')
663
+ .replace(/\\/$/, '');
664
+
665
+ routes.set(routePath, async (req${useTypescript ? ': Request' : ''}) => {
666
+ const module = await import(\`\${appDir}/\${file}\`);
206
667
  const method = req.method.toUpperCase();
207
668
  const handler = module[method];
208
669
 
@@ -214,13 +675,13 @@ async function scanRoutes() {
214
675
  }
215
676
  }
216
677
 
217
- // Main server
678
+ // Start server
218
679
  async function main() {
219
680
  await scanRoutes();
220
681
 
221
682
  Bun.serve({
222
683
  port: PORT,
223
- async fetch(req: Request) {
684
+ async fetch(req${useTypescript ? ': Request' : ''}) {
224
685
  const url = new URL(req.url);
225
686
  let pathname = url.pathname;
226
687
 
@@ -229,7 +690,7 @@ async function main() {
229
690
  pathname = pathname.slice(0, -1);
230
691
  }
231
692
 
232
- // Try to match route
693
+ // Match route
233
694
  const handler = routes.get(pathname);
234
695
  if (handler) {
235
696
  return handler(req);
@@ -248,266 +709,165 @@ async function main() {
248
709
  });
249
710
 
250
711
  console.log(\`
251
- 🔥 Oven is ready!
712
+ ${c.green}✓${c.reset} Ready in \${Date.now() - startTime}ms
252
713
 
253
- Local: http://localhost:\${PORT}
254
- Network: http://0.0.0.0:\${PORT}
714
+ ${c.dim}➜${c.reset} Local: ${c.cyan}http://localhost:\${PORT}${c.reset}
715
+ ${c.dim}➜${c.reset} Network: ${c.cyan}http://0.0.0.0:\${PORT}${c.reset}
255
716
  \`);
256
717
  }
257
718
 
258
- main();
259
- `;
260
- fs.writeFileSync(path.join(projectDir, 'server.ts'), serverFile);
261
-
262
- // app/layout.tsx
263
- const rootLayout = `import type { LayoutProps } from './types';
264
-
265
- export default function RootLayout({ children }: LayoutProps) {
266
- return \`
267
- <div id="__oven">
268
- <nav style="
269
- background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
270
- padding: 1rem 2rem;
271
- display: flex;
272
- justify-content: space-between;
273
- align-items: center;
274
- ">
275
- <a href="/" style="color: white; text-decoration: none; font-weight: bold; font-size: 1.25rem;">
276
- 🔥 ${projectName}
277
- </a>
278
- <div style="display: flex; gap: 1.5rem;">
279
- <a href="/" style="color: rgba(255,255,255,0.9); text-decoration: none;">Home</a>
280
- <a href="/about" style="color: rgba(255,255,255,0.9); text-decoration: none;">About</a>
281
- <a href="/api/hello" style="color: rgba(255,255,255,0.9); text-decoration: none;">API</a>
282
- </div>
283
- </nav>
284
- <main style="min-height: calc(100vh - 60px);">
285
- \${children}
286
- </main>
287
- <style>
288
- * { margin: 0; padding: 0; box-sizing: border-box; }
289
- body { font-family: system-ui, -apple-system, sans-serif; }
290
- </style>
291
- </div>
292
- \`;
293
- }
294
- `;
295
- fs.writeFileSync(path.join(projectDir, 'app', 'layout.tsx'), rootLayout);
296
-
297
- // app/types.ts
298
- const typesFile = `export interface PageProps {
299
- params: Record<string, string>;
300
- searchParams: Record<string, string>;
301
- }
302
-
303
- export interface LayoutProps {
304
- children: string;
305
- params: Record<string, string>;
306
- }
307
-
308
- export interface Metadata {
309
- title?: string | { default: string; template?: string };
310
- description?: string;
311
- keywords?: string[];
312
- }
719
+ const startTime = Date.now();
720
+ main().catch(console.error);
313
721
  `;
314
- fs.writeFileSync(path.join(projectDir, 'app', 'types.ts'), typesFile);
315
-
316
- // app/page.tsx
317
- const homePage = `import type { PageProps, Metadata } from './types';
318
-
319
- export const metadata: Metadata = {
320
- title: '${projectName}',
321
- description: 'Built with Oven - A Next.js-style framework for Bun',
322
- };
323
-
324
- export default function HomePage({ searchParams }: PageProps) {
325
- return \`
326
- <div style="max-width: 800px; margin: 0 auto; padding: 4rem 2rem; text-align: center;">
327
- <h1 style="font-size: 3rem; margin-bottom: 1rem; background: linear-gradient(135deg, #ff6b35, #f7931e); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
328
- Welcome to ${projectName} 🔥
329
- </h1>
330
- <p style="color: #666; font-size: 1.2rem; margin-bottom: 2rem;">
331
- Get started by editing <code style="background: #f5f5f5; padding: 0.25rem 0.5rem; border-radius: 4px;">app/page.tsx</code>
332
- </p>
722
+ fs.writeFileSync(path.join(useSrcDir ? path.join(projectDir, 'src') : projectDir, `server.${ext}`), serverContent);
333
723
 
334
- <div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
335
- <a href="/about" style="
336
- background: linear-gradient(135deg, #ff6b35, #f7931e);
337
- color: white;
338
- padding: 0.75rem 1.5rem;
339
- border-radius: 8px;
340
- text-decoration: none;
341
- font-weight: 500;
342
- ">
343
- About Page →
344
- </a>
345
- <a href="/api/hello" style="
346
- background: #333;
347
- color: white;
348
- padding: 0.75rem 1.5rem;
349
- border-radius: 8px;
350
- text-decoration: none;
351
- font-weight: 500;
352
- ">
353
- API Example
354
- </a>
355
- </div>
356
-
357
- <div style="margin-top: 4rem; padding: 2rem; background: #f9f9f9; border-radius: 12px; text-align: left;">
358
- <h3 style="margin-bottom: 1rem;">📁 Project Structure</h3>
359
- <pre style="font-size: 0.9rem; color: #666;">
360
- ${projectName}/
361
- ├── app/
362
- │ ├── layout.tsx # Root layout
363
- │ ├── page.tsx # Home page (/)
364
- │ ├── about/
365
- │ │ └── page.tsx # About page (/about)
366
- │ └── api/
367
- │ └── hello/
368
- │ └── route.ts # API route (/api/hello)
369
- ├── public/ # Static files
370
- ├── server.ts # Bun server
371
- └── package.json
372
- </pre>
373
- </div>
374
- </div>
375
- \`;
376
- }
377
- `;
378
- fs.writeFileSync(path.join(projectDir, 'app', 'page.tsx'), homePage);
379
-
380
- // app/about/page.tsx
381
- const aboutPage = `import type { PageProps, Metadata } from '../types';
382
-
383
- export const metadata: Metadata = {
384
- title: 'About',
385
- description: 'About ${projectName}',
386
- };
724
+ // .gitignore
725
+ const gitignoreContent = `# Dependencies
726
+ node_modules
727
+ .pnpm-store
387
728
 
388
- export default function AboutPage({ params }: PageProps) {
389
- return \`
390
- <div style="max-width: 800px; margin: 0 auto; padding: 4rem 2rem;">
391
- <h1 style="font-size: 2.5rem; margin-bottom: 1rem;">About ${projectName}</h1>
392
- <p style="color: #666; line-height: 1.8; margin-bottom: 1.5rem;">
393
- This is the about page of your Oven application.
394
- Built with Bun for maximum performance.
395
- </p>
729
+ # Build
730
+ dist
731
+ .oven
732
+ .next
733
+ out
396
734
 
397
- <div style="background: #fff5f2; padding: 1.5rem; border-radius: 12px; border-left: 4px solid #ff6b35;">
398
- <h3 style="margin-bottom: 0.5rem;">🚀 Why Oven?</h3>
399
- <ul style="color: #666; line-height: 1.8; margin-left: 1.5rem;">
400
- <li>Built for Bun runtime - 4x faster than Node.js</li>
401
- <li>Next.js-style file-based routing</li>
402
- <li>Zero configuration needed</li>
403
- <li>Full TypeScript support</li>
404
- </ul>
405
- </div>
735
+ # Environment
736
+ .env
737
+ .env.local
738
+ .env.*.local
406
739
 
407
- <a href="/" style="color: #ff6b35; margin-top: 2rem; display: inline-block; text-decoration: none;">
408
- ← Back to Home
409
- </a>
410
- </div>
411
- \`;
412
- }
413
- `;
414
- fs.writeFileSync(path.join(projectDir, 'app', 'about', 'page.tsx'), aboutPage);
740
+ # Logs
741
+ *.log
742
+ npm-debug.log*
415
743
 
416
- // app/api/hello/route.ts
417
- const apiRoute = `// API Route: /api/hello
744
+ # OS
745
+ .DS_Store
746
+ Thumbs.db
418
747
 
419
- export async function GET(request: Request) {
420
- return Response.json({
421
- message: 'Hello from ${projectName}!',
422
- timestamp: new Date().toISOString(),
423
- framework: 'Oven 🔥',
424
- });
425
- }
748
+ # IDE
749
+ .vscode
750
+ .idea
751
+ *.swp
752
+ *.swo
426
753
 
427
- export async function POST(request: Request) {
428
- const body = await request.json().catch(() => ({}));
429
- return Response.json({
430
- message: 'Data received!',
431
- data: body,
432
- });
433
- }
434
- `;
435
- fs.writeFileSync(path.join(projectDir, 'app', 'api', 'hello', 'route.ts'), apiRoute);
754
+ # Testing
755
+ coverage
436
756
 
437
- // .gitignore
438
- const gitignore = `node_modules
439
- dist
440
- .oven
441
- *.log
442
- .DS_Store
443
- .env
444
- .env.local
757
+ # Misc
758
+ *.tsbuildinfo
445
759
  `;
446
- fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignore);
760
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent);
447
761
 
448
- // README.md
449
- const readme = `# ${projectName}
762
+ // README.md
763
+ const readmeContent = `# ${projectName}
450
764
 
451
- Built with [Oven](https://github.com/oven-ttta/oven-framework) - A Next.js-style framework for Bun 🔥
765
+ This is a [Oven](https://github.com/oven-ttta/oven-framework) project bootstrapped with \`create-oven\`.
452
766
 
453
767
  ## Getting Started
454
768
 
769
+ First, install dependencies:
770
+
455
771
  \`\`\`bash
456
- # Install dependencies
457
772
  bun install
773
+ \`\`\`
458
774
 
459
- # Start development server
460
- bun run dev
461
-
462
- # Build for production
463
- bun run build
775
+ Then, run the development server:
464
776
 
465
- # Start production server
466
- bun run start
777
+ \`\`\`bash
778
+ bun run dev
467
779
  \`\`\`
468
780
 
469
- Open [http://localhost:3000](http://localhost:3000) to see your app.
781
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
782
+
783
+ You can start editing the page by modifying \`${useSrcDir ? 'src/' : ''}app/page.${ext}x\`. The page auto-updates as you edit the file.
470
784
 
471
785
  ## Project Structure
472
786
 
473
787
  \`\`\`
474
788
  ${projectName}/
475
- ├── app/
476
- │ ├── layout.tsx # Root layout
477
- │ ├── page.tsx # Home page (/)
789
+ ├── ${useSrcDir ? 'src/' : ''}app/
790
+ │ ├── layout.${ext}x # Root layout
791
+ │ ├── page.${ext}x # Home page (/)
792
+ │ ├── globals.css # Global styles
478
793
  │ ├── about/
479
- │ │ └── page.tsx # About page (/about)
794
+ │ │ └── page.${ext}x # About page (/about)
480
795
  │ └── api/
481
796
  │ └── hello/
482
- │ └── route.ts # API route (/api/hello)
483
- ├── public/ # Static files
484
- ├── server.ts # Bun server
797
+ │ └── route.${ext} # API route (/api/hello)
798
+ ├── public/ # Static files
799
+ ${useTailwind ? '├── tailwind.config.js # Tailwind config\n' : ''}├── oven.config.${ext} # Oven config
485
800
  └── package.json
486
801
  \`\`\`
487
802
 
488
803
  ## Learn More
489
804
 
490
- - [Oven GitHub](https://github.com/oven-ttta/oven-framework)
805
+ - [Oven Documentation](https://github.com/oven-ttta/oven-framework)
491
806
  - [Bun Documentation](https://bun.sh/docs)
807
+ ${useTailwind ? '- [Tailwind CSS](https://tailwindcss.com/docs)\n' : ''}
808
+ ## Deploy
809
+
810
+ Deploy your Oven app with Docker or Vercel.
811
+
812
+ ### Docker
813
+
814
+ \`\`\`bash
815
+ docker build -t ${projectName} .
816
+ docker run -p 3000:3000 ${projectName}
817
+ \`\`\`
818
+
819
+ ### Vercel
820
+
821
+ \`\`\`bash
822
+ vercel --prod
823
+ \`\`\`
492
824
  `;
493
- fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
825
+ fs.writeFileSync(path.join(projectDir, 'README.md'), readmeContent);
826
+
827
+ spin3.stop('Created app files');
828
+
829
+ // Install dependencies
830
+ const packageManager = hasFlag('--use-npm') ? 'npm' : 'bun';
831
+ const spin4 = spinner(`Installing dependencies with ${packageManager}...`);
832
+
833
+ try {
834
+ execSync(`${packageManager} install`, {
835
+ cwd: projectDir,
836
+ stdio: 'pipe',
837
+ });
838
+ spin4.stop(`Installed dependencies with ${packageManager}`);
839
+ } catch (e) {
840
+ spin4.fail(`Failed to install dependencies. Run '${packageManager} install' manually.`);
841
+ }
494
842
 
495
- success(`
496
- ✅ Project created successfully!
843
+ // Success message
844
+ console.log(`
845
+ ${c.green}${c.bold}Success!${c.reset} Created ${c.cyan}${projectName}${c.reset} at ${projectDir}
497
846
 
498
- ${COLORS.yellow}Next steps:${COLORS.reset}
847
+ ${c.dim}Inside that directory, you can run several commands:${c.reset}
499
848
 
500
- cd ${projectName}
501
- bun install
502
- bun run dev
849
+ ${c.cyan}bun run dev${c.reset}
850
+ Starts the development server.
503
851
 
504
- Open ${COLORS.cyan}http://localhost:3000${COLORS.reset} in your browser.
852
+ ${c.cyan}bun run build${c.reset}
853
+ Builds the app for production.
505
854
 
506
- ${COLORS.bright}Happy cooking! 🔥${COLORS.reset}
507
- `);
855
+ ${c.cyan}bun run start${c.reset}
856
+ Runs the built app in production mode.
857
+
858
+ ${c.dim}We suggest that you begin by typing:${c.reset}
859
+
860
+ ${c.cyan}cd${c.reset} ${projectName}
861
+ ${c.cyan}bun run dev${c.reset}
862
+
863
+ ${c.bold}Happy coding! 🔥${c.reset}
864
+ `);
865
+
866
+ } catch (err) {
867
+ console.error(`\n${c.red}Error: ${err.message}${c.reset}`);
868
+ rl.close();
869
+ process.exit(1);
870
+ }
508
871
  }
509
872
 
510
- main().catch((err) => {
511
- error(`Error: ${err.message}`);
512
- process.exit(1);
513
- });
873
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-oven",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Create a new Oven project - Next.js-style framework for Bun",
5
5
  "type": "module",
6
6
  "bin": {