create-oven 0.2.4 → 0.3.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.
- package/index.js +311 -439
- 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.1');
|
|
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.1 ║
|
|
142
99
|
║ ║
|
|
143
100
|
║ Next.js-style framework for Bun ║
|
|
144
101
|
║ ║
|
|
@@ -148,113 +105,93 @@ ${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
|
-
}
|
|
157
|
+
fs.mkdirSync(path.join(projectDir, 'app'), { recursive: true });
|
|
158
|
+
fs.mkdirSync(path.join(projectDir, 'public'), { recursive: true });
|
|
225
159
|
|
|
226
|
-
|
|
160
|
+
// ============ public/oven.svg ============
|
|
161
|
+
fs.writeFileSync(path.join(projectDir, 'public', 'oven.svg'), `<svg width="100" height="24" viewBox="0 0 100 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
162
|
+
<text x="0" y="20" font-family="system-ui, sans-serif" font-size="20" font-weight="bold" fill="currentColor">🔥 Oven</text>
|
|
163
|
+
</svg>`);
|
|
227
164
|
|
|
228
|
-
//
|
|
229
|
-
|
|
165
|
+
// ============ public/github.svg ============
|
|
166
|
+
fs.writeFileSync(path.join(projectDir, 'public', 'github.svg'), `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
167
|
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
|
168
|
+
</svg>`);
|
|
230
169
|
|
|
231
|
-
// package.json
|
|
232
|
-
const
|
|
170
|
+
// ============ package.json ============
|
|
171
|
+
const spin1 = spinner('Creating package.json...');
|
|
233
172
|
const pkg = {
|
|
234
173
|
name: projectName,
|
|
235
174
|
version: '0.1.0',
|
|
236
175
|
private: true,
|
|
237
176
|
scripts: {
|
|
238
|
-
dev:
|
|
239
|
-
build:
|
|
177
|
+
dev: 'bun run --hot server.tsx',
|
|
178
|
+
build: 'bun build ./server.tsx --outdir ./dist --target bun',
|
|
240
179
|
start: 'bun run dist/server.js',
|
|
241
|
-
lint:
|
|
180
|
+
...(useEslint && { lint: 'eslint .' }),
|
|
242
181
|
},
|
|
243
182
|
dependencies: {},
|
|
244
183
|
devDependencies: {
|
|
245
|
-
...(useTypescript && { '@types/bun': 'latest', 'typescript': '^5
|
|
246
|
-
...(
|
|
247
|
-
...(
|
|
184
|
+
...(useTypescript && { '@types/bun': 'latest', 'typescript': '^5' }),
|
|
185
|
+
...(useTailwind && { '@tailwindcss/postcss': '^4', 'tailwindcss': '^4' }),
|
|
186
|
+
...(useEslint && { 'eslint': '^9' }),
|
|
248
187
|
},
|
|
249
188
|
};
|
|
250
|
-
// Remove undefined scripts
|
|
251
|
-
Object.keys(pkg.scripts).forEach(key => {
|
|
252
|
-
if (pkg.scripts[key] === undefined) delete pkg.scripts[key];
|
|
253
|
-
});
|
|
254
189
|
fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
190
|
+
spin1.stop('Created package.json');
|
|
255
191
|
|
|
256
|
-
// tsconfig.json
|
|
192
|
+
// ============ tsconfig.json ============
|
|
257
193
|
if (useTypescript) {
|
|
194
|
+
const spin2 = spinner('Creating tsconfig.json...');
|
|
258
195
|
const tsconfig = {
|
|
259
196
|
compilerOptions: {
|
|
260
197
|
target: 'ES2017',
|
|
@@ -271,116 +208,66 @@ ${c.bold}${c.cyan} ╔═══════════════════
|
|
|
271
208
|
jsx: 'preserve',
|
|
272
209
|
incremental: true,
|
|
273
210
|
types: ['bun-types'],
|
|
274
|
-
paths: {
|
|
275
|
-
[importAlias]: [useSrcDir ? './src/*' : './*'],
|
|
276
|
-
},
|
|
277
|
-
baseUrl: '.',
|
|
211
|
+
paths: { '@/*': ['./*'] },
|
|
278
212
|
},
|
|
279
|
-
include: [
|
|
213
|
+
include: ['**/*.ts', '**/*.tsx'],
|
|
280
214
|
exclude: ['node_modules', 'dist'],
|
|
281
215
|
};
|
|
282
216
|
fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
217
|
+
spin2.stop('Created tsconfig.json');
|
|
283
218
|
}
|
|
284
219
|
|
|
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
|
|
220
|
+
// ============ postcss.config.mjs ============
|
|
305
221
|
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 = {
|
|
222
|
+
const spin3 = spinner('Creating postcss.config.mjs...');
|
|
223
|
+
fs.writeFileSync(path.join(projectDir, 'postcss.config.mjs'), `const config = {
|
|
336
224
|
plugins: {
|
|
337
|
-
tailwindcss: {},
|
|
338
|
-
autoprefixer: {},
|
|
225
|
+
"@tailwindcss/postcss": {},
|
|
339
226
|
},
|
|
340
227
|
};
|
|
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
228
|
|
|
355
229
|
export default config;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
spin2.stop('Created configuration files');
|
|
230
|
+
`);
|
|
231
|
+
spin3.stop('Created postcss.config.mjs');
|
|
232
|
+
}
|
|
360
233
|
|
|
361
|
-
//
|
|
362
|
-
|
|
234
|
+
// ============ eslint.config.mjs ============
|
|
235
|
+
if (useEslint) {
|
|
236
|
+
const spin4 = spinner('Creating eslint.config.mjs...');
|
|
237
|
+
fs.writeFileSync(path.join(projectDir, 'eslint.config.mjs'), `import js from "@eslint/js";
|
|
238
|
+
|
|
239
|
+
export default [
|
|
240
|
+
js.configs.recommended,
|
|
241
|
+
{
|
|
242
|
+
rules: {
|
|
243
|
+
"no-unused-vars": "warn",
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
`);
|
|
248
|
+
spin4.stop('Created eslint.config.mjs');
|
|
249
|
+
}
|
|
363
250
|
|
|
364
|
-
// globals.css
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
@tailwind utilities;
|
|
251
|
+
// ============ app/globals.css ============
|
|
252
|
+
const spin5 = spinner('Creating app/globals.css...');
|
|
253
|
+
const globalsCss = useTailwind ? `@import "tailwindcss";
|
|
368
254
|
|
|
369
255
|
:root {
|
|
370
|
-
--
|
|
371
|
-
--
|
|
256
|
+
--background: #ffffff;
|
|
257
|
+
--foreground: #171717;
|
|
372
258
|
}
|
|
373
259
|
|
|
374
260
|
@media (prefers-color-scheme: dark) {
|
|
375
261
|
:root {
|
|
376
|
-
--
|
|
377
|
-
--
|
|
262
|
+
--background: #0a0a0a;
|
|
263
|
+
--foreground: #ededed;
|
|
378
264
|
}
|
|
379
265
|
}
|
|
380
266
|
|
|
381
267
|
body {
|
|
382
|
-
|
|
383
|
-
|
|
268
|
+
background: var(--background);
|
|
269
|
+
color: var(--foreground);
|
|
270
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
384
271
|
}
|
|
385
272
|
` : `* {
|
|
386
273
|
margin: 0;
|
|
@@ -389,193 +276,229 @@ body {
|
|
|
389
276
|
}
|
|
390
277
|
|
|
391
278
|
:root {
|
|
392
|
-
--
|
|
393
|
-
--
|
|
394
|
-
--oven-primary: #ff6b35;
|
|
395
|
-
--oven-secondary: #f7931e;
|
|
279
|
+
--background: #ffffff;
|
|
280
|
+
--foreground: #171717;
|
|
396
281
|
}
|
|
397
282
|
|
|
398
283
|
body {
|
|
399
|
-
|
|
400
|
-
color:
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
a {
|
|
405
|
-
color: inherit;
|
|
406
|
-
text-decoration: none;
|
|
284
|
+
background: var(--background);
|
|
285
|
+
color: var(--foreground);
|
|
286
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
407
287
|
}
|
|
408
288
|
`;
|
|
409
|
-
fs.writeFileSync(path.join(
|
|
410
|
-
|
|
411
|
-
// favicon.ico (placeholder - just create the public dir marker)
|
|
412
|
-
fs.writeFileSync(path.join(projectDir, 'public', '.gitkeep'), '');
|
|
289
|
+
fs.writeFileSync(path.join(projectDir, 'app', 'globals.css'), globalsCss);
|
|
290
|
+
spin5.stop('Created app/globals.css');
|
|
413
291
|
|
|
414
|
-
//
|
|
415
|
-
const
|
|
292
|
+
// ============ app/layout.tsx ============
|
|
293
|
+
const spin6 = spinner(`Creating app/layout.${ext}...`);
|
|
294
|
+
const layoutContent = useTypescript ? `import "./globals.css";
|
|
416
295
|
|
|
417
|
-
export const metadata = {
|
|
418
|
-
title:
|
|
419
|
-
|
|
420
|
-
template: '%s | ${projectName}',
|
|
421
|
-
},
|
|
422
|
-
description: 'Built with Oven - A Next.js-style framework for Bun',
|
|
296
|
+
export const metadata${useTypescript ? ': { title: string; description: string }' : ''} = {
|
|
297
|
+
title: "Create Oven App",
|
|
298
|
+
description: "Generated by create-oven",
|
|
423
299
|
};
|
|
424
300
|
|
|
425
|
-
export default function RootLayout({
|
|
426
|
-
|
|
427
|
-
|
|
301
|
+
export default function RootLayout({
|
|
302
|
+
children,
|
|
303
|
+
}: Readonly<{
|
|
304
|
+
children: string;
|
|
305
|
+
}>) {
|
|
306
|
+
return (
|
|
307
|
+
\`<!DOCTYPE html>
|
|
428
308
|
<html lang="en">
|
|
429
309
|
<head>
|
|
430
|
-
<meta charset="UTF-8"
|
|
431
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"
|
|
310
|
+
<meta charset="UTF-8" />
|
|
311
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
312
|
+
<title>\${metadata.title}</title>
|
|
313
|
+
<meta name="description" content="\${metadata.description}" />
|
|
432
314
|
<link rel="icon" href="/favicon.ico" />
|
|
315
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
316
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
317
|
+
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono&display=swap" rel="stylesheet" />
|
|
433
318
|
</head>
|
|
434
|
-
<body
|
|
319
|
+
<body class="antialiased" style="font-family: 'Geist', sans-serif;">
|
|
435
320
|
\${children}
|
|
436
321
|
</body>
|
|
437
|
-
</html
|
|
438
|
-
|
|
322
|
+
</html>\`
|
|
323
|
+
);
|
|
439
324
|
}
|
|
440
|
-
|
|
441
|
-
fs.writeFileSync(path.join(appDir, `layout.${ext}`), layoutContent);
|
|
325
|
+
` : `import "./globals.css";
|
|
442
326
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
description: 'Welcome to ${projectName}',
|
|
327
|
+
export const metadata = {
|
|
328
|
+
title: "Create Oven App",
|
|
329
|
+
description: "Generated by create-oven",
|
|
447
330
|
};
|
|
448
331
|
|
|
449
|
-
export default function
|
|
450
|
-
return
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
332
|
+
export default function RootLayout({ children }) {
|
|
333
|
+
return (
|
|
334
|
+
\`<!DOCTYPE html>
|
|
335
|
+
<html lang="en">
|
|
336
|
+
<head>
|
|
337
|
+
<meta charset="UTF-8" />
|
|
338
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
339
|
+
<title>\${metadata.title}</title>
|
|
340
|
+
<meta name="description" content="\${metadata.description}" />
|
|
341
|
+
<link rel="icon" href="/favicon.ico" />
|
|
342
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
343
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
344
|
+
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono&display=swap" rel="stylesheet" />
|
|
345
|
+
</head>
|
|
346
|
+
<body class="antialiased" style="font-family: 'Geist', sans-serif;">
|
|
347
|
+
\${children}
|
|
348
|
+
</body>
|
|
349
|
+
</html>\`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
`;
|
|
353
|
+
fs.writeFileSync(path.join(projectDir, 'app', `layout.${ext}`), layoutContent);
|
|
354
|
+
spin6.stop(`Created app/layout.${ext}`);
|
|
355
|
+
|
|
356
|
+
// ============ app/page.tsx ============
|
|
357
|
+
const spin7 = spinner(`Creating app/page.${ext}...`);
|
|
358
|
+
const pageContent = useTailwind ? `export default function Home() {
|
|
359
|
+
return (
|
|
360
|
+
\`<div class="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
|
361
|
+
<main 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 sm:items-start">
|
|
362
|
+
<img
|
|
363
|
+
class="dark:invert"
|
|
364
|
+
src="/oven.svg"
|
|
365
|
+
alt="Oven logo"
|
|
366
|
+
width="100"
|
|
367
|
+
height="24"
|
|
368
|
+
/>
|
|
369
|
+
<div class="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
|
370
|
+
<h1 class="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
|
371
|
+
Get started by editing app/page.${ext}
|
|
372
|
+
</h1>
|
|
373
|
+
<p class="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
374
|
+
Looking for a starting point? Head over to the{" "}
|
|
375
|
+
<a
|
|
376
|
+
href="https://github.com/oven-ttta/oven-framework"
|
|
377
|
+
class="font-medium text-zinc-950 dark:text-zinc-50"
|
|
378
|
+
>
|
|
379
|
+
Documentation
|
|
380
|
+
</a>{" "}
|
|
381
|
+
or{" "}
|
|
382
|
+
<a
|
|
383
|
+
href="https://bun.sh/docs"
|
|
384
|
+
class="font-medium text-zinc-950 dark:text-zinc-50"
|
|
385
|
+
>
|
|
386
|
+
Bun Docs
|
|
387
|
+
</a>.
|
|
388
|
+
</p>
|
|
389
|
+
</div>
|
|
390
|
+
<div class="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
391
|
+
<a
|
|
392
|
+
class="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
|
393
|
+
href="https://github.com/oven-ttta/oven-framework"
|
|
394
|
+
target="_blank"
|
|
395
|
+
rel="noopener noreferrer"
|
|
396
|
+
style="background: #171717; color: white;"
|
|
397
|
+
>
|
|
398
|
+
<img
|
|
399
|
+
class="dark:invert"
|
|
400
|
+
src="/github.svg"
|
|
401
|
+
alt="GitHub"
|
|
402
|
+
width="16"
|
|
403
|
+
height="16"
|
|
404
|
+
/>
|
|
405
|
+
GitHub
|
|
467
406
|
</a>
|
|
468
|
-
<a
|
|
469
|
-
|
|
407
|
+
<a
|
|
408
|
+
class="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
409
|
+
href="https://bun.sh/docs"
|
|
410
|
+
target="_blank"
|
|
411
|
+
rel="noopener noreferrer"
|
|
412
|
+
>
|
|
413
|
+
Bun Docs
|
|
470
414
|
</a>
|
|
471
415
|
</div>
|
|
472
|
-
</
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
416
|
+
</main>
|
|
417
|
+
</div>\`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
` : `export default function Home() {
|
|
421
|
+
return (
|
|
422
|
+
\`<div style="display: flex; min-height: 100vh; align-items: center; justify-content: center; background: #fafafa;">
|
|
423
|
+
<main style="display: flex; flex-direction: column; align-items: center; justify-content: space-between; max-width: 48rem; padding: 8rem 4rem; background: white;">
|
|
424
|
+
<div style="font-size: 4rem; margin-bottom: 2rem;">🔥</div>
|
|
425
|
+
<div style="display: flex; flex-direction: column; align-items: center; gap: 1.5rem; text-align: center;">
|
|
426
|
+
<h1 style="font-size: 1.875rem; font-weight: 600; line-height: 2.5rem; color: black;">
|
|
427
|
+
Get started by editing app/page.${ext}
|
|
428
|
+
</h1>
|
|
429
|
+
<p style="max-width: 28rem; font-size: 1.125rem; line-height: 2rem; color: #666;">
|
|
430
|
+
Looking for a starting point? Head over to the
|
|
431
|
+
<a href="https://github.com/oven-ttta/oven-framework" style="font-weight: 500; color: black;">Documentation</a>
|
|
432
|
+
or
|
|
433
|
+
<a href="https://bun.sh/docs" style="font-weight: 500; color: black;">Bun Docs</a>.
|
|
486
434
|
</p>
|
|
487
435
|
</div>
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
436
|
+
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
|
|
437
|
+
<a
|
|
438
|
+
href="https://github.com/oven-ttta/oven-framework"
|
|
439
|
+
target="_blank"
|
|
440
|
+
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;"
|
|
441
|
+
>
|
|
442
|
+
GitHub
|
|
443
|
+
</a>
|
|
444
|
+
<a
|
|
445
|
+
href="https://bun.sh/docs"
|
|
446
|
+
target="_blank"
|
|
447
|
+
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;"
|
|
448
|
+
>
|
|
449
|
+
Bun Docs
|
|
450
|
+
</a>
|
|
494
451
|
</div>
|
|
495
|
-
</
|
|
496
|
-
</
|
|
497
|
-
|
|
452
|
+
</main>
|
|
453
|
+
</div>\`
|
|
454
|
+
);
|
|
498
455
|
}
|
|
499
456
|
`;
|
|
500
|
-
fs.writeFileSync(path.join(
|
|
457
|
+
fs.writeFileSync(path.join(projectDir, 'app', `page.${ext}`), pageContent);
|
|
458
|
+
spin7.stop(`Created app/page.${ext}`);
|
|
501
459
|
|
|
502
|
-
//
|
|
460
|
+
// ============ server.tsx ============
|
|
461
|
+
const spin8 = spinner(`Creating server.${ext}...`);
|
|
503
462
|
const serverContent = `/**
|
|
504
|
-
* Oven Server
|
|
505
|
-
*
|
|
463
|
+
* Oven Server
|
|
464
|
+
* Powered by Bun
|
|
506
465
|
*/
|
|
507
466
|
|
|
508
|
-
const PORT = parseInt(process.env.PORT ||
|
|
467
|
+
const PORT = parseInt(process.env.PORT || "3000");
|
|
509
468
|
|
|
510
|
-
//
|
|
511
|
-
const routes${useTypescript ? '
|
|
469
|
+
// Simple router
|
|
470
|
+
const routes = new Map${useTypescript ? '<string, (req: Request) => Promise<Response>>' : ''}();
|
|
512
471
|
|
|
513
|
-
// Scan and register routes
|
|
514
472
|
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;
|
|
526
|
-
|
|
527
|
-
routes.set(normalizedPath, async (req${useTypescript ? ': Request' : ''}) => {
|
|
528
|
-
try {
|
|
529
|
-
const module = await import(\`\${appDir}/\${file}\`);
|
|
530
|
-
const content = await module.default({ params: {}, searchParams: {} });
|
|
473
|
+
const appDir = "./app";
|
|
531
474
|
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
}
|
|
475
|
+
// Scan for page files
|
|
476
|
+
const glob = new Bun.Glob("**/page.{tsx,jsx,ts,js}");
|
|
556
477
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
.replace(
|
|
562
|
-
.replace(/^route\\.(ts|js)$/, '')
|
|
563
|
-
.replace(/\\/$/, '');
|
|
478
|
+
for await (const file of glob.scan({ cwd: appDir })) {
|
|
479
|
+
const routePath = "/" + file
|
|
480
|
+
.replace(/\\/page\\.(tsx|jsx|ts|js)$/, "")
|
|
481
|
+
.replace(/^page\\.(tsx|jsx|ts|js)$/, "")
|
|
482
|
+
.replace(/\\/$/, "") || "/";
|
|
564
483
|
|
|
565
|
-
routes.set(routePath, async (req${useTypescript ? ': Request' : ''}) => {
|
|
484
|
+
routes.set(routePath === "" ? "/" : routePath, async (req${useTypescript ? ': Request' : ''}) => {
|
|
566
485
|
const module = await import(\`\${appDir}/\${file}\`);
|
|
567
|
-
const
|
|
568
|
-
const handler = module[method];
|
|
486
|
+
const content = await module.default();
|
|
569
487
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
488
|
+
// Wrap with layout
|
|
489
|
+
let html = content;
|
|
490
|
+
try {
|
|
491
|
+
const layout = await import(\`\${appDir}/layout.tsx\`);
|
|
492
|
+
html = await layout.default({ children: content });
|
|
493
|
+
} catch {}
|
|
494
|
+
|
|
495
|
+
return new Response(html, {
|
|
496
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
497
|
+
});
|
|
574
498
|
});
|
|
575
499
|
}
|
|
576
500
|
}
|
|
577
501
|
|
|
578
|
-
// Start server
|
|
579
502
|
async function main() {
|
|
580
503
|
await scanRoutes();
|
|
581
504
|
|
|
@@ -585,94 +508,76 @@ async function main() {
|
|
|
585
508
|
const url = new URL(req.url);
|
|
586
509
|
let pathname = url.pathname;
|
|
587
510
|
|
|
588
|
-
|
|
589
|
-
if (pathname !== '/' && pathname.endsWith('/')) {
|
|
511
|
+
if (pathname !== "/" && pathname.endsWith("/")) {
|
|
590
512
|
pathname = pathname.slice(0, -1);
|
|
591
513
|
}
|
|
592
514
|
|
|
593
|
-
//
|
|
515
|
+
// Check routes
|
|
594
516
|
const handler = routes.get(pathname);
|
|
595
517
|
if (handler) {
|
|
596
518
|
return handler(req);
|
|
597
519
|
}
|
|
598
520
|
|
|
599
521
|
// Static files
|
|
600
|
-
const publicPath =
|
|
522
|
+
const publicPath = "./public" + pathname;
|
|
601
523
|
const file = Bun.file(publicPath);
|
|
602
524
|
if (await file.exists()) {
|
|
603
525
|
return new Response(file);
|
|
604
526
|
}
|
|
605
527
|
|
|
606
|
-
|
|
607
|
-
return new Response('Not Found', { status: 404 });
|
|
528
|
+
return new Response("Not Found", { status: 404 });
|
|
608
529
|
},
|
|
609
530
|
});
|
|
610
531
|
|
|
611
532
|
console.log(\`
|
|
612
|
-
${c.green}
|
|
533
|
+
${c.green}▲${c.reset} Ready in \${Date.now() - start}ms
|
|
613
534
|
|
|
614
535
|
${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
536
|
\`);
|
|
617
537
|
}
|
|
618
538
|
|
|
619
|
-
const
|
|
620
|
-
main()
|
|
539
|
+
const start = Date.now();
|
|
540
|
+
main();
|
|
621
541
|
`;
|
|
622
|
-
fs.writeFileSync(path.join(
|
|
542
|
+
fs.writeFileSync(path.join(projectDir, `server.${ext}`), serverContent);
|
|
543
|
+
spin8.stop(`Created server.${ext}`);
|
|
623
544
|
|
|
624
|
-
// .gitignore
|
|
625
|
-
const
|
|
545
|
+
// ============ .gitignore ============
|
|
546
|
+
const spin9 = spinner('Creating .gitignore...');
|
|
547
|
+
fs.writeFileSync(path.join(projectDir, '.gitignore'), `# Dependencies
|
|
626
548
|
node_modules
|
|
627
549
|
.pnpm-store
|
|
628
550
|
|
|
629
551
|
# Build
|
|
630
552
|
dist
|
|
631
553
|
.oven
|
|
632
|
-
.next
|
|
633
|
-
out
|
|
634
554
|
|
|
635
|
-
#
|
|
555
|
+
# Env
|
|
636
556
|
.env
|
|
637
557
|
.env.local
|
|
638
558
|
.env.*.local
|
|
639
559
|
|
|
640
560
|
# Logs
|
|
641
561
|
*.log
|
|
642
|
-
npm-debug.log*
|
|
643
562
|
|
|
644
563
|
# OS
|
|
645
564
|
.DS_Store
|
|
646
|
-
Thumbs.db
|
|
647
565
|
|
|
648
566
|
# IDE
|
|
649
567
|
.vscode
|
|
650
568
|
.idea
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
# Testing
|
|
655
|
-
coverage
|
|
656
|
-
|
|
657
|
-
# Misc
|
|
658
|
-
*.tsbuildinfo
|
|
659
|
-
`;
|
|
660
|
-
fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent);
|
|
569
|
+
`);
|
|
570
|
+
spin9.stop('Created .gitignore');
|
|
661
571
|
|
|
662
|
-
// README.md
|
|
663
|
-
const
|
|
572
|
+
// ============ README.md ============
|
|
573
|
+
const spin10 = spinner('Creating README.md...');
|
|
574
|
+
fs.writeFileSync(path.join(projectDir, 'README.md'), `# ${projectName}
|
|
664
575
|
|
|
665
|
-
This is
|
|
576
|
+
This is an [Oven](https://github.com/oven-ttta/oven-framework) project bootstrapped with \`create-oven\`.
|
|
666
577
|
|
|
667
578
|
## Getting Started
|
|
668
579
|
|
|
669
|
-
First,
|
|
670
|
-
|
|
671
|
-
\`\`\`bash
|
|
672
|
-
bun install
|
|
673
|
-
\`\`\`
|
|
674
|
-
|
|
675
|
-
Then, run the development server:
|
|
580
|
+
First, run the development server:
|
|
676
581
|
|
|
677
582
|
\`\`\`bash
|
|
678
583
|
bun run dev
|
|
@@ -680,68 +585,37 @@ bun run dev
|
|
|
680
585
|
|
|
681
586
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
682
587
|
|
|
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
|
-
\`\`\`
|
|
588
|
+
You can start editing the page by modifying \`app/page.${ext}\`. The page auto-updates as you edit the file.
|
|
697
589
|
|
|
698
590
|
## Learn More
|
|
699
591
|
|
|
700
|
-
- [Oven
|
|
592
|
+
- [Oven GitHub](https://github.com/oven-ttta/oven-framework)
|
|
701
593
|
- [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');
|
|
594
|
+
`);
|
|
595
|
+
spin10.stop('Created README.md');
|
|
723
596
|
|
|
724
|
-
// Install dependencies
|
|
597
|
+
// ============ Install dependencies ============
|
|
725
598
|
if (!noInstall) {
|
|
726
|
-
|
|
727
|
-
const
|
|
728
|
-
|
|
599
|
+
console.log();
|
|
600
|
+
const spin11 = spinner('Installing dependencies...');
|
|
729
601
|
try {
|
|
730
|
-
execSync(
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
602
|
+
execSync('bun install', { cwd: projectDir, stdio: 'pipe' });
|
|
603
|
+
spin11.stop('Installed dependencies');
|
|
604
|
+
} catch {
|
|
605
|
+
try {
|
|
606
|
+
execSync('npm install', { cwd: projectDir, stdio: 'pipe' });
|
|
607
|
+
spin11.stop('Installed dependencies');
|
|
608
|
+
} catch {
|
|
609
|
+
spin11.fail('Failed to install. Run "bun install" manually.');
|
|
610
|
+
}
|
|
737
611
|
}
|
|
738
612
|
}
|
|
739
613
|
|
|
740
|
-
// Success
|
|
614
|
+
// Success
|
|
741
615
|
console.log(`
|
|
742
|
-
${c.green}
|
|
616
|
+
${c.green}Success!${c.reset} Created ${c.cyan}${projectName}${c.reset} at ${projectDir}
|
|
743
617
|
|
|
744
|
-
|
|
618
|
+
Inside that directory, you can run:
|
|
745
619
|
|
|
746
620
|
${c.cyan}bun run dev${c.reset}
|
|
747
621
|
Starts the development server.
|
|
@@ -750,14 +624,12 @@ ${c.dim}Inside that directory, you can run several commands:${c.reset}
|
|
|
750
624
|
Builds the app for production.
|
|
751
625
|
|
|
752
626
|
${c.cyan}bun run start${c.reset}
|
|
753
|
-
Runs the built app
|
|
627
|
+
Runs the built app.
|
|
754
628
|
|
|
755
|
-
|
|
629
|
+
We suggest that you begin by typing:
|
|
756
630
|
|
|
757
631
|
${c.cyan}cd${c.reset} ${projectName}
|
|
758
632
|
${c.cyan}bun run dev${c.reset}
|
|
759
|
-
|
|
760
|
-
${c.bold}Happy coding! 🔥${c.reset}
|
|
761
633
|
`);
|
|
762
634
|
|
|
763
635
|
} catch (err) {
|