create-oven 0.2.4 → 0.3.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.
- package/index.js +228 -434
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -3,21 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* create-oven - Create a new Oven project
|
|
5
5
|
* Like create-next-app but for Bun
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npx create-oven my-app
|
|
9
|
-
* bunx create-oven my-app
|
|
10
6
|
*/
|
|
11
7
|
|
|
12
8
|
import fs from 'fs';
|
|
13
9
|
import path from 'path';
|
|
14
10
|
import readline from 'readline';
|
|
15
|
-
import { fileURLToPath } from 'url';
|
|
16
11
|
import { execSync } from 'child_process';
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// Colors for terminal output
|
|
13
|
+
// Colors
|
|
21
14
|
const c = {
|
|
22
15
|
reset: '\x1b[0m',
|
|
23
16
|
bold: '\x1b[1m',
|
|
@@ -26,13 +19,9 @@ const c = {
|
|
|
26
19
|
green: '\x1b[32m',
|
|
27
20
|
yellow: '\x1b[33m',
|
|
28
21
|
blue: '\x1b[34m',
|
|
29
|
-
magenta: '\x1b[35m',
|
|
30
22
|
cyan: '\x1b[36m',
|
|
31
|
-
white: '\x1b[37m',
|
|
32
|
-
bgBlue: '\x1b[44m',
|
|
33
23
|
};
|
|
34
24
|
|
|
35
|
-
// Create readline interface for prompts
|
|
36
25
|
function createPrompt() {
|
|
37
26
|
return readline.createInterface({
|
|
38
27
|
input: process.stdin,
|
|
@@ -40,34 +29,16 @@ function createPrompt() {
|
|
|
40
29
|
});
|
|
41
30
|
}
|
|
42
31
|
|
|
43
|
-
// Ask a yes/no question
|
|
44
32
|
async function askYesNo(rl, question, defaultValue = true) {
|
|
45
|
-
const defaultText = defaultValue ? 'Yes' : 'No';
|
|
46
33
|
const hint = defaultValue ? '(Y/n)' : '(y/N)';
|
|
47
|
-
|
|
48
34
|
return new Promise((resolve) => {
|
|
49
35
|
rl.question(`${c.cyan}?${c.reset} ${question} ${c.dim}${hint}${c.reset} `, (answer) => {
|
|
50
|
-
if (!answer.trim())
|
|
51
|
-
|
|
52
|
-
} else {
|
|
53
|
-
resolve(answer.toLowerCase().startsWith('y'));
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
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);
|
|
36
|
+
if (!answer.trim()) resolve(defaultValue);
|
|
37
|
+
else resolve(answer.toLowerCase().startsWith('y'));
|
|
66
38
|
});
|
|
67
39
|
});
|
|
68
40
|
}
|
|
69
41
|
|
|
70
|
-
// Display a spinner
|
|
71
42
|
function spinner(text) {
|
|
72
43
|
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
73
44
|
let i = 0;
|
|
@@ -75,7 +46,6 @@ function spinner(text) {
|
|
|
75
46
|
process.stdout.write(`\r${c.cyan}${frames[i]}${c.reset} ${text}`);
|
|
76
47
|
i = (i + 1) % frames.length;
|
|
77
48
|
}, 80);
|
|
78
|
-
|
|
79
49
|
return {
|
|
80
50
|
stop: (finalText) => {
|
|
81
51
|
clearInterval(interval);
|
|
@@ -88,57 +58,44 @@ function spinner(text) {
|
|
|
88
58
|
};
|
|
89
59
|
}
|
|
90
60
|
|
|
91
|
-
// Main function
|
|
92
61
|
async function main() {
|
|
93
62
|
const args = process.argv.slice(2);
|
|
94
63
|
|
|
95
|
-
// Show help
|
|
96
64
|
if (args.includes('--help') || args.includes('-h')) {
|
|
97
65
|
console.log(`
|
|
98
66
|
${c.bold}${c.cyan}create-oven${c.reset} - Create a new Oven project
|
|
99
67
|
|
|
100
68
|
${c.yellow}Usage:${c.reset}
|
|
101
69
|
npx create-oven [project-name] [options]
|
|
102
|
-
bunx create-oven [project-name] [options]
|
|
103
70
|
|
|
104
71
|
${c.yellow}Options:${c.reset}
|
|
105
|
-
--
|
|
106
|
-
--
|
|
107
|
-
--tailwind
|
|
108
|
-
--
|
|
109
|
-
--
|
|
110
|
-
--no-eslint Don't
|
|
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
|
|
72
|
+
--ts, --typescript Use TypeScript (default)
|
|
73
|
+
--js, --javascript Use JavaScript
|
|
74
|
+
--tailwind Use Tailwind CSS (default)
|
|
75
|
+
--eslint Use ESLint (default)
|
|
76
|
+
--no-tailwind Don't use Tailwind CSS
|
|
77
|
+
--no-eslint Don't use ESLint
|
|
117
78
|
--no-install Skip installing dependencies
|
|
118
79
|
-y, --yes Use defaults without prompts
|
|
119
|
-
-h, --help Show this help
|
|
80
|
+
-h, --help Show this help
|
|
120
81
|
-v, --version Show version
|
|
121
82
|
|
|
122
83
|
${c.yellow}Examples:${c.reset}
|
|
123
84
|
npx create-oven my-app
|
|
124
|
-
npx create-oven my-app --
|
|
125
|
-
bunx create-oven my-app --src-dir
|
|
126
|
-
|
|
127
|
-
${c.cyan}Learn more: https://github.com/oven-ttta/oven-framework${c.reset}
|
|
85
|
+
npx create-oven my-app --js --no-tailwind
|
|
128
86
|
`);
|
|
129
87
|
process.exit(0);
|
|
130
88
|
}
|
|
131
89
|
|
|
132
|
-
// Show version
|
|
133
90
|
if (args.includes('--version') || args.includes('-v')) {
|
|
134
|
-
console.log('create-oven v0.
|
|
91
|
+
console.log('create-oven v0.3.0');
|
|
135
92
|
process.exit(0);
|
|
136
93
|
}
|
|
137
94
|
|
|
138
95
|
console.log(`
|
|
139
96
|
${c.bold}${c.cyan} ╔═══════════════════════════════════════╗
|
|
140
97
|
║ ║
|
|
141
|
-
║ 🔥 Create Oven App v0.
|
|
98
|
+
║ 🔥 Create Oven App v0.3.0 ║
|
|
142
99
|
║ ║
|
|
143
100
|
║ Next.js-style framework for Bun ║
|
|
144
101
|
║ ║
|
|
@@ -148,113 +105,83 @@ ${c.bold}${c.cyan} ╔═══════════════════
|
|
|
148
105
|
const rl = createPrompt();
|
|
149
106
|
|
|
150
107
|
try {
|
|
108
|
+
const hasFlag = (flag) => args.includes(flag);
|
|
109
|
+
const skipPrompts = hasFlag('--yes') || hasFlag('-y');
|
|
110
|
+
const noInstall = hasFlag('--no-install');
|
|
111
|
+
|
|
151
112
|
// Get project name
|
|
152
113
|
let projectName = args.find(arg => !arg.startsWith('-'));
|
|
153
|
-
|
|
154
114
|
if (!projectName) {
|
|
155
|
-
projectName = await
|
|
115
|
+
projectName = await new Promise((resolve) => {
|
|
116
|
+
rl.question(`${c.cyan}?${c.reset} What is your project named? ${c.dim}(my-app)${c.reset} `, (answer) => {
|
|
117
|
+
resolve(answer.trim() || 'my-app');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
156
120
|
}
|
|
157
121
|
|
|
158
122
|
const projectDir = path.join(process.cwd(), projectName);
|
|
159
123
|
|
|
160
|
-
// Check if directory exists
|
|
161
124
|
if (fs.existsSync(projectDir)) {
|
|
162
125
|
console.log(`\n${c.red}✗${c.reset} Directory "${projectName}" already exists!`);
|
|
163
126
|
rl.close();
|
|
164
127
|
process.exit(1);
|
|
165
128
|
}
|
|
166
129
|
|
|
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
130
|
console.log();
|
|
174
131
|
|
|
175
|
-
//
|
|
176
|
-
const useTypescript = hasFlag('--
|
|
177
|
-
hasFlag('--
|
|
132
|
+
// Options
|
|
133
|
+
const useTypescript = hasFlag('--js') || hasFlag('--javascript') ? false :
|
|
134
|
+
hasFlag('--ts') || hasFlag('--typescript') ? true :
|
|
178
135
|
skipPrompts ? true :
|
|
179
136
|
await askYesNo(rl, 'Would you like to use TypeScript?', true);
|
|
180
137
|
|
|
181
|
-
const
|
|
182
|
-
hasFlag('--
|
|
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') :
|
|
138
|
+
const useTailwind = hasFlag('--no-tailwind') ? false :
|
|
139
|
+
hasFlag('--tailwind') ? true :
|
|
188
140
|
skipPrompts ? true :
|
|
189
141
|
await askYesNo(rl, 'Would you like to use Tailwind CSS?', true);
|
|
190
142
|
|
|
191
|
-
const
|
|
192
|
-
hasFlag('--
|
|
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') :
|
|
143
|
+
const useEslint = hasFlag('--no-eslint') ? false :
|
|
144
|
+
hasFlag('--eslint') ? true :
|
|
198
145
|
skipPrompts ? true :
|
|
199
|
-
await askYesNo(rl, 'Would you like to use
|
|
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?', '@/*'));
|
|
146
|
+
await askYesNo(rl, 'Would you like to use ESLint?', true);
|
|
204
147
|
|
|
205
148
|
rl.close();
|
|
206
149
|
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(`Creating a new Oven app in ${c.green}${projectDir}${c.reset}.`);
|
|
207
152
|
console.log();
|
|
208
153
|
|
|
209
|
-
|
|
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');
|
|
154
|
+
const ext = useTypescript ? 'tsx' : 'js';
|
|
215
155
|
|
|
216
156
|
// Create directories
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
path.join(projectDir, 'public'),
|
|
220
|
-
];
|
|
221
|
-
|
|
222
|
-
for (const dir of dirs) {
|
|
223
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
spin.stop('Created project structure');
|
|
227
|
-
|
|
228
|
-
// Create files
|
|
229
|
-
const spin2 = spinner('Creating configuration files...');
|
|
157
|
+
fs.mkdirSync(path.join(projectDir, 'app'), { recursive: true });
|
|
158
|
+
fs.mkdirSync(path.join(projectDir, 'public'), { recursive: true });
|
|
230
159
|
|
|
231
|
-
// package.json
|
|
232
|
-
const
|
|
160
|
+
// ============ package.json ============
|
|
161
|
+
const spin1 = spinner('Creating package.json...');
|
|
233
162
|
const pkg = {
|
|
234
163
|
name: projectName,
|
|
235
164
|
version: '0.1.0',
|
|
236
165
|
private: true,
|
|
237
166
|
scripts: {
|
|
238
|
-
dev:
|
|
239
|
-
build:
|
|
167
|
+
dev: 'bun run --hot server.tsx',
|
|
168
|
+
build: 'bun build ./server.tsx --outdir ./dist --target bun',
|
|
240
169
|
start: 'bun run dist/server.js',
|
|
241
|
-
lint:
|
|
170
|
+
...(useEslint && { lint: 'eslint .' }),
|
|
242
171
|
},
|
|
243
172
|
dependencies: {},
|
|
244
173
|
devDependencies: {
|
|
245
|
-
...(useTypescript && { '@types/bun': 'latest', 'typescript': '^5
|
|
246
|
-
...(
|
|
247
|
-
...(
|
|
174
|
+
...(useTypescript && { '@types/bun': 'latest', 'typescript': '^5' }),
|
|
175
|
+
...(useTailwind && { '@tailwindcss/postcss': '^4', 'tailwindcss': '^4' }),
|
|
176
|
+
...(useEslint && { 'eslint': '^9' }),
|
|
248
177
|
},
|
|
249
178
|
};
|
|
250
|
-
// Remove undefined scripts
|
|
251
|
-
Object.keys(pkg.scripts).forEach(key => {
|
|
252
|
-
if (pkg.scripts[key] === undefined) delete pkg.scripts[key];
|
|
253
|
-
});
|
|
254
179
|
fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
180
|
+
spin1.stop('Created package.json');
|
|
255
181
|
|
|
256
|
-
// tsconfig.json
|
|
182
|
+
// ============ tsconfig.json ============
|
|
257
183
|
if (useTypescript) {
|
|
184
|
+
const spin2 = spinner('Creating tsconfig.json...');
|
|
258
185
|
const tsconfig = {
|
|
259
186
|
compilerOptions: {
|
|
260
187
|
target: 'ES2017',
|
|
@@ -271,116 +198,66 @@ ${c.bold}${c.cyan} ╔═══════════════════
|
|
|
271
198
|
jsx: 'preserve',
|
|
272
199
|
incremental: true,
|
|
273
200
|
types: ['bun-types'],
|
|
274
|
-
paths: {
|
|
275
|
-
[importAlias]: [useSrcDir ? './src/*' : './*'],
|
|
276
|
-
},
|
|
277
|
-
baseUrl: '.',
|
|
201
|
+
paths: { '@/*': ['./*'] },
|
|
278
202
|
},
|
|
279
|
-
include: [
|
|
203
|
+
include: ['**/*.ts', '**/*.tsx'],
|
|
280
204
|
exclude: ['node_modules', 'dist'],
|
|
281
205
|
};
|
|
282
206
|
fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
207
|
+
spin2.stop('Created tsconfig.json');
|
|
283
208
|
}
|
|
284
209
|
|
|
285
|
-
//
|
|
286
|
-
if (useEslint) {
|
|
287
|
-
const eslintConfig = {
|
|
288
|
-
extends: [
|
|
289
|
-
'eslint:recommended',
|
|
290
|
-
...(useTypescript ? ['plugin:@typescript-eslint/recommended'] : []),
|
|
291
|
-
],
|
|
292
|
-
parser: useTypescript ? '@typescript-eslint/parser' : undefined,
|
|
293
|
-
plugins: useTypescript ? ['@typescript-eslint'] : [],
|
|
294
|
-
parserOptions: {
|
|
295
|
-
ecmaVersion: 'latest',
|
|
296
|
-
sourceType: 'module',
|
|
297
|
-
},
|
|
298
|
-
rules: {},
|
|
299
|
-
ignorePatterns: ['node_modules/', 'dist/'],
|
|
300
|
-
};
|
|
301
|
-
fs.writeFileSync(path.join(projectDir, '.eslintrc.json'), JSON.stringify(eslintConfig, null, 2));
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Tailwind config
|
|
210
|
+
// ============ postcss.config.mjs ============
|
|
305
211
|
if (useTailwind) {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
content: [
|
|
309
|
-
'./${useSrcDir ? 'src/' : ''}app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
310
|
-
'./${useSrcDir ? 'src/' : ''}components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
311
|
-
],
|
|
312
|
-
theme: {
|
|
313
|
-
extend: {
|
|
314
|
-
colors: {
|
|
315
|
-
oven: {
|
|
316
|
-
50: '#fff5f2',
|
|
317
|
-
100: '#ffe6de',
|
|
318
|
-
200: '#ffc9b8',
|
|
319
|
-
300: '#ffa088',
|
|
320
|
-
400: '#ff6b35',
|
|
321
|
-
500: '#f7531e',
|
|
322
|
-
600: '#e63d0a',
|
|
323
|
-
700: '#bf3009',
|
|
324
|
-
800: '#99290d',
|
|
325
|
-
900: '#7d2510',
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
plugins: [],
|
|
331
|
-
};
|
|
332
|
-
`;
|
|
333
|
-
fs.writeFileSync(path.join(projectDir, 'tailwind.config.js'), tailwindConfig);
|
|
334
|
-
|
|
335
|
-
const postcssConfig = `module.exports = {
|
|
212
|
+
const spin3 = spinner('Creating postcss.config.mjs...');
|
|
213
|
+
fs.writeFileSync(path.join(projectDir, 'postcss.config.mjs'), `const config = {
|
|
336
214
|
plugins: {
|
|
337
|
-
tailwindcss: {},
|
|
338
|
-
autoprefixer: {},
|
|
215
|
+
"@tailwindcss/postcss": {},
|
|
339
216
|
},
|
|
340
217
|
};
|
|
341
|
-
`;
|
|
342
|
-
fs.writeFileSync(path.join(projectDir, 'postcss.config.js'), postcssConfig);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// oven.config.ts
|
|
346
|
-
const ovenConfig = `${useTypescript ? "import type { OvenConfig } from './app/types';\n\n" : ''}const config${useTypescript ? ': OvenConfig' : ''} = {
|
|
347
|
-
// Server configuration
|
|
348
|
-
port: 3000,
|
|
349
|
-
|
|
350
|
-
// Directories
|
|
351
|
-
appDir: '${useSrcDir ? 'src/' : ''}app',
|
|
352
|
-
publicDir: 'public',
|
|
353
|
-
};
|
|
354
218
|
|
|
355
219
|
export default config;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
spin2.stop('Created configuration files');
|
|
220
|
+
`);
|
|
221
|
+
spin3.stop('Created postcss.config.mjs');
|
|
222
|
+
}
|
|
360
223
|
|
|
361
|
-
//
|
|
362
|
-
|
|
224
|
+
// ============ eslint.config.mjs ============
|
|
225
|
+
if (useEslint) {
|
|
226
|
+
const spin4 = spinner('Creating eslint.config.mjs...');
|
|
227
|
+
fs.writeFileSync(path.join(projectDir, 'eslint.config.mjs'), `import js from "@eslint/js";
|
|
228
|
+
|
|
229
|
+
export default [
|
|
230
|
+
js.configs.recommended,
|
|
231
|
+
{
|
|
232
|
+
rules: {
|
|
233
|
+
"no-unused-vars": "warn",
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
`);
|
|
238
|
+
spin4.stop('Created eslint.config.mjs');
|
|
239
|
+
}
|
|
363
240
|
|
|
364
|
-
// globals.css
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
@tailwind utilities;
|
|
241
|
+
// ============ app/globals.css ============
|
|
242
|
+
const spin5 = spinner('Creating app/globals.css...');
|
|
243
|
+
const globalsCss = useTailwind ? `@import "tailwindcss";
|
|
368
244
|
|
|
369
245
|
:root {
|
|
370
|
-
--
|
|
371
|
-
--
|
|
246
|
+
--background: #ffffff;
|
|
247
|
+
--foreground: #171717;
|
|
372
248
|
}
|
|
373
249
|
|
|
374
250
|
@media (prefers-color-scheme: dark) {
|
|
375
251
|
:root {
|
|
376
|
-
--
|
|
377
|
-
--
|
|
252
|
+
--background: #0a0a0a;
|
|
253
|
+
--foreground: #ededed;
|
|
378
254
|
}
|
|
379
255
|
}
|
|
380
256
|
|
|
381
257
|
body {
|
|
382
|
-
|
|
383
|
-
|
|
258
|
+
background: var(--background);
|
|
259
|
+
color: var(--foreground);
|
|
260
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
384
261
|
}
|
|
385
262
|
` : `* {
|
|
386
263
|
margin: 0;
|
|
@@ -389,193 +266,161 @@ body {
|
|
|
389
266
|
}
|
|
390
267
|
|
|
391
268
|
:root {
|
|
392
|
-
--
|
|
393
|
-
--
|
|
394
|
-
--oven-primary: #ff6b35;
|
|
395
|
-
--oven-secondary: #f7931e;
|
|
269
|
+
--background: #ffffff;
|
|
270
|
+
--foreground: #171717;
|
|
396
271
|
}
|
|
397
272
|
|
|
398
273
|
body {
|
|
399
|
-
|
|
400
|
-
color:
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
a {
|
|
405
|
-
color: inherit;
|
|
406
|
-
text-decoration: none;
|
|
274
|
+
background: var(--background);
|
|
275
|
+
color: var(--foreground);
|
|
276
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
407
277
|
}
|
|
408
278
|
`;
|
|
409
|
-
fs.writeFileSync(path.join(
|
|
410
|
-
|
|
411
|
-
// favicon.ico (placeholder - just create the public dir marker)
|
|
412
|
-
fs.writeFileSync(path.join(projectDir, 'public', '.gitkeep'), '');
|
|
279
|
+
fs.writeFileSync(path.join(projectDir, 'app', 'globals.css'), globalsCss);
|
|
280
|
+
spin5.stop('Created app/globals.css');
|
|
413
281
|
|
|
414
|
-
//
|
|
415
|
-
const
|
|
282
|
+
// ============ app/layout.tsx ============
|
|
283
|
+
const spin6 = spinner(`Creating app/layout.${ext}...`);
|
|
284
|
+
const layoutContent = useTypescript ? `import "./globals.css";
|
|
416
285
|
|
|
417
286
|
export const metadata = {
|
|
418
|
-
title:
|
|
419
|
-
|
|
420
|
-
template: '%s | ${projectName}',
|
|
421
|
-
},
|
|
422
|
-
description: 'Built with Oven - A Next.js-style framework for Bun',
|
|
287
|
+
title: "Create Oven App",
|
|
288
|
+
description: "Generated by create-oven",
|
|
423
289
|
};
|
|
424
290
|
|
|
425
|
-
export default function RootLayout({
|
|
291
|
+
export default function RootLayout({
|
|
292
|
+
children,
|
|
293
|
+
}: {
|
|
294
|
+
children: string;
|
|
295
|
+
}) {
|
|
426
296
|
return \`
|
|
427
297
|
<!DOCTYPE html>
|
|
428
298
|
<html lang="en">
|
|
429
299
|
<head>
|
|
430
300
|
<meta charset="UTF-8">
|
|
431
301
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
432
|
-
<
|
|
302
|
+
<title>\${metadata.title}</title>
|
|
303
|
+
<meta name="description" content="\${metadata.description}">
|
|
304
|
+
<link rel="icon" href="/favicon.ico">
|
|
433
305
|
</head>
|
|
434
|
-
<body
|
|
306
|
+
<body>
|
|
435
307
|
\${children}
|
|
436
308
|
</body>
|
|
437
309
|
</html>
|
|
438
310
|
\`;
|
|
439
311
|
}
|
|
440
|
-
|
|
441
|
-
fs.writeFileSync(path.join(appDir, `layout.${ext}`), layoutContent);
|
|
312
|
+
` : `import "./globals.css";
|
|
442
313
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
description: 'Welcome to ${projectName}',
|
|
314
|
+
export const metadata = {
|
|
315
|
+
title: "Create Oven App",
|
|
316
|
+
description: "Generated by create-oven",
|
|
447
317
|
};
|
|
448
318
|
|
|
449
|
-
export default function
|
|
319
|
+
export default function RootLayout({ children }) {
|
|
450
320
|
return \`
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
GitHub ⭐
|
|
470
|
-
</a>
|
|
471
|
-
</div>
|
|
472
|
-
</div>
|
|
473
|
-
|
|
474
|
-
<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;"'}>
|
|
475
|
-
<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;"'}>
|
|
476
|
-
<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>
|
|
477
|
-
<p ${useTailwind ? 'class="text-gray-600 text-sm"' : 'style="color: #666; font-size: 0.9rem;"'}>
|
|
478
|
-
Create routes by adding files to the app directory. Just like Next.js!
|
|
479
|
-
</p>
|
|
480
|
-
</div>
|
|
321
|
+
<!DOCTYPE html>
|
|
322
|
+
<html lang="en">
|
|
323
|
+
<head>
|
|
324
|
+
<meta charset="UTF-8">
|
|
325
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
326
|
+
<title>\${metadata.title}</title>
|
|
327
|
+
<meta name="description" content="\${metadata.description}">
|
|
328
|
+
<link rel="icon" href="/favicon.ico">
|
|
329
|
+
</head>
|
|
330
|
+
<body>
|
|
331
|
+
\${children}
|
|
332
|
+
</body>
|
|
333
|
+
</html>
|
|
334
|
+
\`;
|
|
335
|
+
}
|
|
336
|
+
`;
|
|
337
|
+
fs.writeFileSync(path.join(projectDir, 'app', `layout.${ext}`), layoutContent);
|
|
338
|
+
spin6.stop(`Created app/layout.${ext}`);
|
|
481
339
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
340
|
+
// ============ app/page.tsx ============
|
|
341
|
+
const spin7 = spinner(`Creating app/page.${ext}...`);
|
|
342
|
+
const tailwindClasses = useTailwind;
|
|
343
|
+
const pageContent = `export default function Home() {
|
|
344
|
+
return \`
|
|
345
|
+
<div ${tailwindClasses ? 'class="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black"' : 'style="display: flex; min-height: 100vh; align-items: center; justify-content: center; background: #fafafa;"'}>
|
|
346
|
+
<main ${tailwindClasses ? 'class="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black"' : 'style="display: flex; flex-direction: column; align-items: center; justify-content: center; max-width: 48rem; padding: 8rem 4rem; background: white;"'}>
|
|
347
|
+
<div ${tailwindClasses ? 'class="text-6xl mb-8"' : 'style="font-size: 4rem; margin-bottom: 2rem;"'}>🔥</div>
|
|
348
|
+
|
|
349
|
+
<div ${tailwindClasses ? 'class="flex flex-col items-center gap-6 text-center"' : 'style="display: flex; flex-direction: column; align-items: center; gap: 1.5rem; text-align: center;"'}>
|
|
350
|
+
<h1 ${tailwindClasses ? 'class="text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50"' : 'style="font-size: 1.875rem; font-weight: 600; line-height: 2.5rem; color: black;"'}>
|
|
351
|
+
Welcome to Oven
|
|
352
|
+
</h1>
|
|
353
|
+
<p ${tailwindClasses ? 'class="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400"' : 'style="max-width: 28rem; font-size: 1.125rem; line-height: 2rem; color: #666;"'}>
|
|
354
|
+
Get started by editing <code ${tailwindClasses ? 'class="bg-zinc-100 dark:bg-zinc-800 px-2 py-1 rounded"' : 'style="background: #f5f5f5; padding: 0.25rem 0.5rem; border-radius: 4px;"'}>app/page.${ext}</code>
|
|
486
355
|
</p>
|
|
487
356
|
</div>
|
|
488
357
|
|
|
489
|
-
<div ${
|
|
490
|
-
<
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
358
|
+
<div ${tailwindClasses ? 'class="flex flex-col gap-4 text-base font-medium sm:flex-row mt-8"' : 'style="display: flex; gap: 1rem; margin-top: 2rem;"'}>
|
|
359
|
+
<a
|
|
360
|
+
${tailwindClasses ? 'class="flex h-12 items-center justify-center gap-2 rounded-full bg-black text-white px-6 hover:bg-zinc-800 transition-colors"' : 'style="display: flex; height: 3rem; align-items: center; justify-content: center; gap: 0.5rem; border-radius: 9999px; background: black; color: white; padding: 0 1.5rem; text-decoration: none;"'}
|
|
361
|
+
href="https://github.com/oven-ttta/oven-framework"
|
|
362
|
+
target="_blank"
|
|
363
|
+
>
|
|
364
|
+
GitHub
|
|
365
|
+
</a>
|
|
366
|
+
<a
|
|
367
|
+
${tailwindClasses ? 'class="flex h-12 items-center justify-center rounded-full border border-zinc-200 dark:border-zinc-700 px-6 hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-colors"' : 'style="display: flex; height: 3rem; align-items: center; justify-content: center; border-radius: 9999px; border: 1px solid #e5e5e5; padding: 0 1.5rem; text-decoration: none; color: inherit;"'}
|
|
368
|
+
href="https://bun.sh/docs"
|
|
369
|
+
target="_blank"
|
|
370
|
+
>
|
|
371
|
+
Bun Docs
|
|
372
|
+
</a>
|
|
494
373
|
</div>
|
|
495
|
-
</
|
|
496
|
-
</
|
|
374
|
+
</main>
|
|
375
|
+
</div>
|
|
497
376
|
\`;
|
|
498
377
|
}
|
|
499
378
|
`;
|
|
500
|
-
fs.writeFileSync(path.join(
|
|
379
|
+
fs.writeFileSync(path.join(projectDir, 'app', `page.${ext}`), pageContent);
|
|
380
|
+
spin7.stop(`Created app/page.${ext}`);
|
|
501
381
|
|
|
502
|
-
//
|
|
382
|
+
// ============ server.tsx ============
|
|
383
|
+
const spin8 = spinner(`Creating server.${ext}...`);
|
|
503
384
|
const serverContent = `/**
|
|
504
|
-
* Oven Server
|
|
505
|
-
*
|
|
385
|
+
* Oven Server
|
|
386
|
+
* Powered by Bun
|
|
506
387
|
*/
|
|
507
388
|
|
|
508
|
-
const PORT = parseInt(process.env.PORT ||
|
|
389
|
+
const PORT = parseInt(process.env.PORT || "3000");
|
|
509
390
|
|
|
510
|
-
//
|
|
511
|
-
const routes${useTypescript ? '
|
|
391
|
+
// Simple router
|
|
392
|
+
const routes = new Map${useTypescript ? '<string, (req: Request) => Promise<Response>>' : ''}();
|
|
512
393
|
|
|
513
|
-
// Scan and register routes
|
|
514
394
|
async function scanRoutes() {
|
|
515
|
-
const appDir =
|
|
516
|
-
|
|
517
|
-
// Scan pages
|
|
518
|
-
const pageGlob = new Bun.Glob('**/page.{ts,tsx,js,jsx}');
|
|
519
|
-
for await (const file of pageGlob.scan({ cwd: appDir })) {
|
|
520
|
-
const routePath = '/' + file
|
|
521
|
-
.replace(/\\/page\\.(ts|tsx|js|jsx)$/, '')
|
|
522
|
-
.replace(/^page\\.(ts|tsx|js|jsx)$/, '')
|
|
523
|
-
.replace(/\\/$/, '') || '/';
|
|
524
|
-
|
|
525
|
-
const normalizedPath = routePath === '' ? '/' : routePath;
|
|
395
|
+
const appDir = "./app";
|
|
526
396
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const module = await import(\`\${appDir}/\${file}\`);
|
|
530
|
-
const content = await module.default({ params: {}, searchParams: {} });
|
|
531
|
-
|
|
532
|
-
// Get layout
|
|
533
|
-
let html = content;
|
|
534
|
-
try {
|
|
535
|
-
const layoutModule = await import(\`\${appDir}/layout.tsx\`);
|
|
536
|
-
html = await layoutModule.default({ children: content, params: {} });
|
|
537
|
-
} catch {}
|
|
538
|
-
|
|
539
|
-
// Metadata
|
|
540
|
-
const metadata = module.metadata || {};
|
|
541
|
-
const title = typeof metadata.title === 'string'
|
|
542
|
-
? metadata.title
|
|
543
|
-
: metadata.title?.default || '${projectName}';
|
|
544
|
-
|
|
545
|
-
return new Response(html, {
|
|
546
|
-
headers: {
|
|
547
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
548
|
-
},
|
|
549
|
-
});
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error('Page error:', error);
|
|
552
|
-
return new Response('Internal Server Error', { status: 500 });
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
}
|
|
397
|
+
// Scan for page files
|
|
398
|
+
const glob = new Bun.Glob("**/page.{tsx,jsx,ts,js}");
|
|
556
399
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
.replace(
|
|
562
|
-
.replace(/^route\\.(ts|js)$/, '')
|
|
563
|
-
.replace(/\\/$/, '');
|
|
400
|
+
for await (const file of glob.scan({ cwd: appDir })) {
|
|
401
|
+
const routePath = "/" + file
|
|
402
|
+
.replace(/\\/page\\.(tsx|jsx|ts|js)$/, "")
|
|
403
|
+
.replace(/^page\\.(tsx|jsx|ts|js)$/, "")
|
|
404
|
+
.replace(/\\/$/, "") || "/";
|
|
564
405
|
|
|
565
|
-
routes.set(routePath, async (req${useTypescript ? ': Request' : ''}) => {
|
|
406
|
+
routes.set(routePath === "" ? "/" : routePath, async (req${useTypescript ? ': Request' : ''}) => {
|
|
566
407
|
const module = await import(\`\${appDir}/\${file}\`);
|
|
567
|
-
const
|
|
568
|
-
const handler = module[method];
|
|
408
|
+
const content = await module.default();
|
|
569
409
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
410
|
+
// Wrap with layout
|
|
411
|
+
let html = content;
|
|
412
|
+
try {
|
|
413
|
+
const layout = await import(\`\${appDir}/layout.tsx\`);
|
|
414
|
+
html = await layout.default({ children: content });
|
|
415
|
+
} catch {}
|
|
416
|
+
|
|
417
|
+
return new Response(html, {
|
|
418
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
419
|
+
});
|
|
574
420
|
});
|
|
575
421
|
}
|
|
576
422
|
}
|
|
577
423
|
|
|
578
|
-
// Start server
|
|
579
424
|
async function main() {
|
|
580
425
|
await scanRoutes();
|
|
581
426
|
|
|
@@ -585,94 +430,76 @@ async function main() {
|
|
|
585
430
|
const url = new URL(req.url);
|
|
586
431
|
let pathname = url.pathname;
|
|
587
432
|
|
|
588
|
-
|
|
589
|
-
if (pathname !== '/' && pathname.endsWith('/')) {
|
|
433
|
+
if (pathname !== "/" && pathname.endsWith("/")) {
|
|
590
434
|
pathname = pathname.slice(0, -1);
|
|
591
435
|
}
|
|
592
436
|
|
|
593
|
-
//
|
|
437
|
+
// Check routes
|
|
594
438
|
const handler = routes.get(pathname);
|
|
595
439
|
if (handler) {
|
|
596
440
|
return handler(req);
|
|
597
441
|
}
|
|
598
442
|
|
|
599
443
|
// Static files
|
|
600
|
-
const publicPath =
|
|
444
|
+
const publicPath = "./public" + pathname;
|
|
601
445
|
const file = Bun.file(publicPath);
|
|
602
446
|
if (await file.exists()) {
|
|
603
447
|
return new Response(file);
|
|
604
448
|
}
|
|
605
449
|
|
|
606
|
-
|
|
607
|
-
return new Response('Not Found', { status: 404 });
|
|
450
|
+
return new Response("Not Found", { status: 404 });
|
|
608
451
|
},
|
|
609
452
|
});
|
|
610
453
|
|
|
611
454
|
console.log(\`
|
|
612
|
-
${c.green}
|
|
455
|
+
${c.green}▲${c.reset} Ready in \${Date.now() - start}ms
|
|
613
456
|
|
|
614
457
|
${c.dim}➜${c.reset} Local: ${c.cyan}http://localhost:\${PORT}${c.reset}
|
|
615
|
-
${c.dim}➜${c.reset} Network: ${c.cyan}http://0.0.0.0:\${PORT}${c.reset}
|
|
616
458
|
\`);
|
|
617
459
|
}
|
|
618
460
|
|
|
619
|
-
const
|
|
620
|
-
main()
|
|
461
|
+
const start = Date.now();
|
|
462
|
+
main();
|
|
621
463
|
`;
|
|
622
|
-
fs.writeFileSync(path.join(
|
|
464
|
+
fs.writeFileSync(path.join(projectDir, `server.${ext}`), serverContent);
|
|
465
|
+
spin8.stop(`Created server.${ext}`);
|
|
623
466
|
|
|
624
|
-
// .gitignore
|
|
625
|
-
const
|
|
467
|
+
// ============ .gitignore ============
|
|
468
|
+
const spin9 = spinner('Creating .gitignore...');
|
|
469
|
+
fs.writeFileSync(path.join(projectDir, '.gitignore'), `# Dependencies
|
|
626
470
|
node_modules
|
|
627
471
|
.pnpm-store
|
|
628
472
|
|
|
629
473
|
# Build
|
|
630
474
|
dist
|
|
631
475
|
.oven
|
|
632
|
-
.next
|
|
633
|
-
out
|
|
634
476
|
|
|
635
|
-
#
|
|
477
|
+
# Env
|
|
636
478
|
.env
|
|
637
479
|
.env.local
|
|
638
480
|
.env.*.local
|
|
639
481
|
|
|
640
482
|
# Logs
|
|
641
483
|
*.log
|
|
642
|
-
npm-debug.log*
|
|
643
484
|
|
|
644
485
|
# OS
|
|
645
486
|
.DS_Store
|
|
646
|
-
Thumbs.db
|
|
647
487
|
|
|
648
488
|
# IDE
|
|
649
489
|
.vscode
|
|
650
490
|
.idea
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
# Testing
|
|
655
|
-
coverage
|
|
656
|
-
|
|
657
|
-
# Misc
|
|
658
|
-
*.tsbuildinfo
|
|
659
|
-
`;
|
|
660
|
-
fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent);
|
|
491
|
+
`);
|
|
492
|
+
spin9.stop('Created .gitignore');
|
|
661
493
|
|
|
662
|
-
// README.md
|
|
663
|
-
const
|
|
494
|
+
// ============ README.md ============
|
|
495
|
+
const spin10 = spinner('Creating README.md...');
|
|
496
|
+
fs.writeFileSync(path.join(projectDir, 'README.md'), `# ${projectName}
|
|
664
497
|
|
|
665
|
-
This is
|
|
498
|
+
This is an [Oven](https://github.com/oven-ttta/oven-framework) project bootstrapped with \`create-oven\`.
|
|
666
499
|
|
|
667
500
|
## Getting Started
|
|
668
501
|
|
|
669
|
-
First,
|
|
670
|
-
|
|
671
|
-
\`\`\`bash
|
|
672
|
-
bun install
|
|
673
|
-
\`\`\`
|
|
674
|
-
|
|
675
|
-
Then, run the development server:
|
|
502
|
+
First, run the development server:
|
|
676
503
|
|
|
677
504
|
\`\`\`bash
|
|
678
505
|
bun run dev
|
|
@@ -680,68 +507,37 @@ bun run dev
|
|
|
680
507
|
|
|
681
508
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
682
509
|
|
|
683
|
-
You can start editing the page by modifying
|
|
684
|
-
|
|
685
|
-
## Project Structure
|
|
686
|
-
|
|
687
|
-
\`\`\`
|
|
688
|
-
${projectName}/
|
|
689
|
-
├── ${useSrcDir ? 'src/' : ''}app/
|
|
690
|
-
│ ├── layout.${ext} # Root layout
|
|
691
|
-
│ ├── page.${ext} # Home page (/)
|
|
692
|
-
│ └── globals.css # Global styles
|
|
693
|
-
├── public/ # Static files
|
|
694
|
-
${useTailwind ? '├── tailwind.config.js # Tailwind config\n' : ''}├── oven.config.${ext} # Oven config
|
|
695
|
-
└── package.json
|
|
696
|
-
\`\`\`
|
|
510
|
+
You can start editing the page by modifying \`app/page.${ext}\`. The page auto-updates as you edit the file.
|
|
697
511
|
|
|
698
512
|
## Learn More
|
|
699
513
|
|
|
700
|
-
- [Oven
|
|
514
|
+
- [Oven GitHub](https://github.com/oven-ttta/oven-framework)
|
|
701
515
|
- [Bun Documentation](https://bun.sh/docs)
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
Deploy your Oven app with Docker or Vercel.
|
|
706
|
-
|
|
707
|
-
### Docker
|
|
708
|
-
|
|
709
|
-
\`\`\`bash
|
|
710
|
-
docker build -t ${projectName} .
|
|
711
|
-
docker run -p 3000:3000 ${projectName}
|
|
712
|
-
\`\`\`
|
|
713
|
-
|
|
714
|
-
### Vercel
|
|
715
|
-
|
|
716
|
-
\`\`\`bash
|
|
717
|
-
vercel --prod
|
|
718
|
-
\`\`\`
|
|
719
|
-
`;
|
|
720
|
-
fs.writeFileSync(path.join(projectDir, 'README.md'), readmeContent);
|
|
721
|
-
|
|
722
|
-
spin3.stop('Created app files');
|
|
516
|
+
`);
|
|
517
|
+
spin10.stop('Created README.md');
|
|
723
518
|
|
|
724
|
-
// Install dependencies
|
|
519
|
+
// ============ Install dependencies ============
|
|
725
520
|
if (!noInstall) {
|
|
726
|
-
|
|
727
|
-
const
|
|
728
|
-
|
|
521
|
+
console.log();
|
|
522
|
+
const spin11 = spinner('Installing dependencies...');
|
|
729
523
|
try {
|
|
730
|
-
execSync(
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
524
|
+
execSync('bun install', { cwd: projectDir, stdio: 'pipe' });
|
|
525
|
+
spin11.stop('Installed dependencies');
|
|
526
|
+
} catch {
|
|
527
|
+
try {
|
|
528
|
+
execSync('npm install', { cwd: projectDir, stdio: 'pipe' });
|
|
529
|
+
spin11.stop('Installed dependencies');
|
|
530
|
+
} catch {
|
|
531
|
+
spin11.fail('Failed to install. Run "bun install" manually.');
|
|
532
|
+
}
|
|
737
533
|
}
|
|
738
534
|
}
|
|
739
535
|
|
|
740
|
-
// Success
|
|
536
|
+
// Success
|
|
741
537
|
console.log(`
|
|
742
|
-
${c.green}
|
|
538
|
+
${c.green}Success!${c.reset} Created ${c.cyan}${projectName}${c.reset} at ${projectDir}
|
|
743
539
|
|
|
744
|
-
|
|
540
|
+
Inside that directory, you can run:
|
|
745
541
|
|
|
746
542
|
${c.cyan}bun run dev${c.reset}
|
|
747
543
|
Starts the development server.
|
|
@@ -750,14 +546,12 @@ ${c.dim}Inside that directory, you can run several commands:${c.reset}
|
|
|
750
546
|
Builds the app for production.
|
|
751
547
|
|
|
752
548
|
${c.cyan}bun run start${c.reset}
|
|
753
|
-
Runs the built app
|
|
549
|
+
Runs the built app.
|
|
754
550
|
|
|
755
|
-
|
|
551
|
+
We suggest that you begin by typing:
|
|
756
552
|
|
|
757
553
|
${c.cyan}cd${c.reset} ${projectName}
|
|
758
554
|
${c.cyan}bun run dev${c.reset}
|
|
759
|
-
|
|
760
|
-
${c.bold}Happy coding! 🔥${c.reset}
|
|
761
555
|
`);
|
|
762
556
|
|
|
763
557
|
} catch (err) {
|