create-oven 0.1.0 → 0.2.1

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