@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
package/src/cli/bin.ts
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Command
|
|
3
|
+
*
|
|
4
|
+
* Build the application for production
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineCommand } from './index';
|
|
8
|
+
import { getOption, hasFlag, type ParsedArgs } from '../core/args';
|
|
9
|
+
import { cliConsole, colors, formatSize, formatDuration } from '../core/console';
|
|
10
|
+
import { spinner } from '../core/spinner';
|
|
11
|
+
import {
|
|
12
|
+
fileExists,
|
|
13
|
+
getProjectRoot,
|
|
14
|
+
isBuenoProject,
|
|
15
|
+
joinPaths,
|
|
16
|
+
readFile,
|
|
17
|
+
deleteDirectory,
|
|
18
|
+
listFiles,
|
|
19
|
+
} from '../utils/fs';
|
|
20
|
+
import { CLIError, CLIErrorType } from '../index';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build targets
|
|
24
|
+
*/
|
|
25
|
+
type BuildTarget = 'bun' | 'node' | 'standalone';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cross-compile targets
|
|
29
|
+
*/
|
|
30
|
+
type CrossCompileTarget = 'linux-x64' | 'linux-arm64' | 'windows-x64' | 'darwin-x64' | 'darwin-arm64';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Find the entry point for the application
|
|
34
|
+
*/
|
|
35
|
+
async function findEntryPoint(projectRoot: string): Promise<string | null> {
|
|
36
|
+
const possibleEntries = [
|
|
37
|
+
'server/main.ts',
|
|
38
|
+
'src/main.ts',
|
|
39
|
+
'src/index.ts',
|
|
40
|
+
'main.ts',
|
|
41
|
+
'index.ts',
|
|
42
|
+
'server.ts',
|
|
43
|
+
'app.ts',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
for (const entry of possibleEntries) {
|
|
47
|
+
const entryPath = joinPaths(projectRoot, entry);
|
|
48
|
+
if (await fileExists(entryPath)) {
|
|
49
|
+
return entry;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Handle build command
|
|
58
|
+
*/
|
|
59
|
+
async function handleBuild(args: ParsedArgs): Promise<void> {
|
|
60
|
+
// Get options
|
|
61
|
+
const target = getOption(args, 'target', {
|
|
62
|
+
name: 'target',
|
|
63
|
+
alias: 't',
|
|
64
|
+
type: 'string',
|
|
65
|
+
default: 'bun',
|
|
66
|
+
description: '',
|
|
67
|
+
}) as BuildTarget;
|
|
68
|
+
|
|
69
|
+
const outDir = getOption<string>(args, 'outdir', {
|
|
70
|
+
name: 'outdir',
|
|
71
|
+
alias: 'o',
|
|
72
|
+
type: 'string',
|
|
73
|
+
default: './dist',
|
|
74
|
+
description: '',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const minify = !hasFlag(args, 'no-minify');
|
|
78
|
+
const sourcemap = hasFlag(args, 'sourcemap');
|
|
79
|
+
const analyze = hasFlag(args, 'analyze');
|
|
80
|
+
const configPath = getOption(args, 'config', {
|
|
81
|
+
name: 'config',
|
|
82
|
+
alias: 'c',
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: '',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Compile options
|
|
88
|
+
const compile = hasFlag(args, 'compile');
|
|
89
|
+
const crossCompile = getOption(args, 'cross-compile', {
|
|
90
|
+
name: 'cross-compile',
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: '',
|
|
93
|
+
}) as CrossCompileTarget | undefined;
|
|
94
|
+
const executableName = getOption<string>(args, 'executable-name', {
|
|
95
|
+
name: 'executable-name',
|
|
96
|
+
type: 'string',
|
|
97
|
+
default: 'main',
|
|
98
|
+
description: '',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Validate cross-compile target
|
|
102
|
+
if (crossCompile) {
|
|
103
|
+
const validCrossCompileTargets: CrossCompileTarget[] = [
|
|
104
|
+
'linux-x64',
|
|
105
|
+
'linux-arm64',
|
|
106
|
+
'windows-x64',
|
|
107
|
+
'darwin-x64',
|
|
108
|
+
'darwin-arm64',
|
|
109
|
+
];
|
|
110
|
+
if (!validCrossCompileTargets.includes(crossCompile)) {
|
|
111
|
+
throw new CLIError(
|
|
112
|
+
`Invalid cross-compile target: ${crossCompile}. Valid targets: ${validCrossCompileTargets.join(', ')}`,
|
|
113
|
+
CLIErrorType.INVALID_ARGS,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Validate that cross-compile requires compile
|
|
119
|
+
if (crossCompile && !compile) {
|
|
120
|
+
throw new CLIError(
|
|
121
|
+
'--cross-compile requires --compile flag',
|
|
122
|
+
CLIErrorType.INVALID_ARGS,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate target
|
|
127
|
+
const validTargets: BuildTarget[] = ['bun', 'node', 'standalone'];
|
|
128
|
+
if (!validTargets.includes(target)) {
|
|
129
|
+
throw new CLIError(
|
|
130
|
+
`Invalid target: ${target}. Valid targets: ${validTargets.join(', ')}`,
|
|
131
|
+
CLIErrorType.INVALID_ARGS,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check if in a Bueno project
|
|
136
|
+
const projectRoot = await getProjectRoot();
|
|
137
|
+
if (!projectRoot) {
|
|
138
|
+
throw new CLIError(
|
|
139
|
+
'Not in a project directory. Run this command from a Bueno project.',
|
|
140
|
+
CLIErrorType.NOT_FOUND,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!(await isBuenoProject())) {
|
|
145
|
+
throw new CLIError(
|
|
146
|
+
'Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.',
|
|
147
|
+
CLIErrorType.NOT_FOUND,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Find entry point
|
|
152
|
+
const entryPoint = await findEntryPoint(projectRoot);
|
|
153
|
+
if (!entryPoint) {
|
|
154
|
+
throw new CLIError(
|
|
155
|
+
'Could not find entry point. Make sure you have a main.ts or index.ts file.',
|
|
156
|
+
CLIErrorType.FILE_NOT_FOUND,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Display build info
|
|
161
|
+
if (compile) {
|
|
162
|
+
cliConsole.header('Compiling Single-File Executable');
|
|
163
|
+
} else {
|
|
164
|
+
cliConsole.header('Building for Production');
|
|
165
|
+
}
|
|
166
|
+
cliConsole.log(`${colors.bold('Entry:')} ${entryPoint}`);
|
|
167
|
+
cliConsole.log(`${colors.bold('Target:')} ${target}`);
|
|
168
|
+
if (compile) {
|
|
169
|
+
cliConsole.log(`${colors.bold('Compile:')} ${colors.green('enabled')}`);
|
|
170
|
+
if (crossCompile) {
|
|
171
|
+
cliConsole.log(`${colors.bold('Cross-compile:')} ${colors.cyan(crossCompile)}`);
|
|
172
|
+
}
|
|
173
|
+
cliConsole.log(`${colors.bold('Executable:')} ${executableName}`);
|
|
174
|
+
}
|
|
175
|
+
cliConsole.log(`${colors.bold('Output:')} ${outDir}`);
|
|
176
|
+
cliConsole.log(`${colors.bold('Minify:')} ${minify ? colors.green('enabled') : colors.red('disabled')}`);
|
|
177
|
+
cliConsole.log(`${colors.bold('Sourcemap:')} ${sourcemap ? colors.green('enabled') : colors.red('disabled')}`);
|
|
178
|
+
cliConsole.log('');
|
|
179
|
+
|
|
180
|
+
const startTime = Date.now();
|
|
181
|
+
const s = spinner('Building...');
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Clean output directory
|
|
185
|
+
const fullOutDir = joinPaths(projectRoot, outDir);
|
|
186
|
+
if (await fileExists(fullOutDir)) {
|
|
187
|
+
await deleteDirectory(fullOutDir);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Handle compile build
|
|
191
|
+
if (compile) {
|
|
192
|
+
// Determine the executable filename
|
|
193
|
+
const isWindows = crossCompile === 'windows-x64';
|
|
194
|
+
const executableFileName = isWindows
|
|
195
|
+
? `${executableName}.exe`
|
|
196
|
+
: executableName;
|
|
197
|
+
const executablePath = joinPaths(fullOutDir, executableFileName);
|
|
198
|
+
|
|
199
|
+
// Build compile options
|
|
200
|
+
const buildOptions: any = {
|
|
201
|
+
entrypoints: [joinPaths(projectRoot, entryPoint)],
|
|
202
|
+
outdir: fullOutDir,
|
|
203
|
+
target: crossCompile || 'bun',
|
|
204
|
+
minify,
|
|
205
|
+
sourcemap: sourcemap ? 'external' : undefined,
|
|
206
|
+
naming: executableFileName,
|
|
207
|
+
compile: true,
|
|
208
|
+
define: {
|
|
209
|
+
'process.env.NODE_ENV': '"production"',
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Build using Bun with compile
|
|
214
|
+
const buildResult = await Bun.build(buildOptions);
|
|
215
|
+
|
|
216
|
+
if (!buildResult.success) {
|
|
217
|
+
s.error();
|
|
218
|
+
for (const error of buildResult.logs) {
|
|
219
|
+
cliConsole.error(error.message);
|
|
220
|
+
}
|
|
221
|
+
throw new CLIError(
|
|
222
|
+
'Compile failed',
|
|
223
|
+
CLIErrorType.TEMPLATE_ERROR,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const elapsed = Date.now() - startTime;
|
|
228
|
+
s.success(`Compile completed in ${formatDuration(elapsed)}`);
|
|
229
|
+
|
|
230
|
+
// Show output info
|
|
231
|
+
cliConsole.log('');
|
|
232
|
+
cliConsole.log(`${colors.bold('Output Executable:')}`);
|
|
233
|
+
|
|
234
|
+
// Get executable file size
|
|
235
|
+
const fs = require('fs');
|
|
236
|
+
let executableSize = 0;
|
|
237
|
+
try {
|
|
238
|
+
const stat = fs.statSync(executablePath);
|
|
239
|
+
executableSize = stat.size;
|
|
240
|
+
} catch (e) {
|
|
241
|
+
// If we can't stat the file, try to get size from build result
|
|
242
|
+
if (buildResult.outputs.length > 0) {
|
|
243
|
+
executableSize = buildResult.outputs[0].size;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
cliConsole.log(` ${colors.cyan(executablePath.replace(projectRoot, '.'))} ${colors.dim(`(${formatSize(executableSize)})`)}`);
|
|
248
|
+
cliConsole.log('');
|
|
249
|
+
|
|
250
|
+
// Show success message with run instructions
|
|
251
|
+
cliConsole.success('Single-file executable created successfully!');
|
|
252
|
+
if (crossCompile) {
|
|
253
|
+
cliConsole.log(`${colors.bold('Target Platform:')} ${crossCompile}`);
|
|
254
|
+
}
|
|
255
|
+
cliConsole.log('');
|
|
256
|
+
cliConsole.info('You can run the executable directly:');
|
|
257
|
+
if (isWindows) {
|
|
258
|
+
cliConsole.log(colors.cyan(` .${outDir}/${executableFileName}`));
|
|
259
|
+
} else {
|
|
260
|
+
cliConsole.log(colors.cyan(` .${outDir}/${executableFileName}`));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Analyze if requested
|
|
264
|
+
if (analyze) {
|
|
265
|
+
cliConsole.log('');
|
|
266
|
+
cliConsole.header('Bundle Analysis');
|
|
267
|
+
cliConsole.log('Output:');
|
|
268
|
+
for (const entry of buildResult.outputs) {
|
|
269
|
+
cliConsole.log(` ${entry.path} (${formatSize(entry.size)})`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Build using Bun (non-compile)
|
|
277
|
+
const buildResult = await Bun.build({
|
|
278
|
+
entrypoints: [joinPaths(projectRoot, entryPoint)],
|
|
279
|
+
outdir: fullOutDir,
|
|
280
|
+
target: target === 'node' ? 'node' : 'bun',
|
|
281
|
+
minify,
|
|
282
|
+
sourcemap: sourcemap ? 'external' : undefined,
|
|
283
|
+
splitting: true,
|
|
284
|
+
format: 'esm',
|
|
285
|
+
external: target === 'standalone' ? [] : ['bun:*'],
|
|
286
|
+
define: {
|
|
287
|
+
'process.env.NODE_ENV': '"production"',
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!buildResult.success) {
|
|
292
|
+
s.error();
|
|
293
|
+
for (const error of buildResult.logs) {
|
|
294
|
+
cliConsole.error(error.message);
|
|
295
|
+
}
|
|
296
|
+
throw new CLIError(
|
|
297
|
+
'Build failed',
|
|
298
|
+
CLIErrorType.TEMPLATE_ERROR,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const elapsed = Date.now() - startTime;
|
|
303
|
+
|
|
304
|
+
// Get output files info
|
|
305
|
+
const outputFiles = await listFiles(fullOutDir, { recursive: true });
|
|
306
|
+
const totalSize = outputFiles.reduce((acc, file) => {
|
|
307
|
+
const stat = require('fs').statSync(file);
|
|
308
|
+
return acc + stat.size;
|
|
309
|
+
}, 0);
|
|
310
|
+
|
|
311
|
+
s.success(`Build completed in ${formatDuration(elapsed)}`);
|
|
312
|
+
|
|
313
|
+
// Show output info
|
|
314
|
+
cliConsole.log('');
|
|
315
|
+
cliConsole.log(`${colors.bold('Output Files:')}`);
|
|
316
|
+
for (const file of outputFiles.slice(0, 10)) {
|
|
317
|
+
const stat = require('fs').statSync(file);
|
|
318
|
+
const relativePath = file.replace(projectRoot, '.');
|
|
319
|
+
cliConsole.log(` ${colors.dim(relativePath)} ${colors.dim(`(${formatSize(stat.size)})`)}`);
|
|
320
|
+
}
|
|
321
|
+
if (outputFiles.length > 10) {
|
|
322
|
+
cliConsole.log(` ${colors.dim(`... and ${outputFiles.length - 10} more files`)}`);
|
|
323
|
+
}
|
|
324
|
+
cliConsole.log('');
|
|
325
|
+
cliConsole.log(`${colors.bold('Total Size:')} ${formatSize(totalSize)}`);
|
|
326
|
+
|
|
327
|
+
// Analyze if requested
|
|
328
|
+
if (analyze) {
|
|
329
|
+
cliConsole.log('');
|
|
330
|
+
cliConsole.header('Bundle Analysis');
|
|
331
|
+
cliConsole.log('Entry points:');
|
|
332
|
+
for (const entry of buildResult.outputs) {
|
|
333
|
+
cliConsole.log(` ${entry.path} (${formatSize(entry.size)})`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Show standalone build info
|
|
338
|
+
if (target === 'standalone') {
|
|
339
|
+
cliConsole.log('');
|
|
340
|
+
cliConsole.info('Standalone bundle created. You can run it with:');
|
|
341
|
+
cliConsole.log(colors.cyan(` bun .${outDir}/${entryPoint.replace('.ts', '.js')}`));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
} catch (error) {
|
|
345
|
+
s.error();
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Register the command
|
|
351
|
+
defineCommand(
|
|
352
|
+
{
|
|
353
|
+
name: 'build',
|
|
354
|
+
description: 'Build the application for production',
|
|
355
|
+
options: [
|
|
356
|
+
{
|
|
357
|
+
name: 'target',
|
|
358
|
+
alias: 't',
|
|
359
|
+
type: 'string',
|
|
360
|
+
default: 'bun',
|
|
361
|
+
description: 'Build target (bun, node, standalone)',
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: 'outdir',
|
|
365
|
+
alias: 'o',
|
|
366
|
+
type: 'string',
|
|
367
|
+
default: './dist',
|
|
368
|
+
description: 'Output directory',
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: 'no-minify',
|
|
372
|
+
type: 'boolean',
|
|
373
|
+
default: false,
|
|
374
|
+
description: 'Disable minification',
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'sourcemap',
|
|
378
|
+
type: 'boolean',
|
|
379
|
+
default: false,
|
|
380
|
+
description: 'Generate source maps',
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: 'analyze',
|
|
384
|
+
type: 'boolean',
|
|
385
|
+
default: false,
|
|
386
|
+
description: 'Analyze bundle size',
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: 'config',
|
|
390
|
+
alias: 'c',
|
|
391
|
+
type: 'string',
|
|
392
|
+
description: 'Path to config file',
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'compile',
|
|
396
|
+
type: 'boolean',
|
|
397
|
+
default: false,
|
|
398
|
+
description: 'Create a single-file executable using Bun compile',
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'cross-compile',
|
|
402
|
+
type: 'string',
|
|
403
|
+
description: 'Cross-compile for different platforms (linux-x64, linux-arm64, windows-x64, darwin-x64, darwin-arm64)',
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'executable-name',
|
|
407
|
+
type: 'string',
|
|
408
|
+
default: 'main',
|
|
409
|
+
description: 'Custom name for the output executable (default: main)',
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
examples: [
|
|
413
|
+
'bueno build',
|
|
414
|
+
'bueno build --target node',
|
|
415
|
+
'bueno build --target standalone',
|
|
416
|
+
'bueno build --sourcemap',
|
|
417
|
+
'bueno build --analyze',
|
|
418
|
+
'bueno build --compile',
|
|
419
|
+
'bueno build --compile --outdir ./bin',
|
|
420
|
+
'bueno build --compile --cross-compile linux-x64',
|
|
421
|
+
'bueno build --compile --executable-name myapp',
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
handleBuild,
|
|
425
|
+
);
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev Command
|
|
3
|
+
*
|
|
4
|
+
* Start the development server with hot reload
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineCommand } from './index';
|
|
8
|
+
import { getOption, hasFlag, type ParsedArgs } from '../core/args';
|
|
9
|
+
import { cliConsole, colors } from '../core/console';
|
|
10
|
+
import { spinner } from '../core/spinner';
|
|
11
|
+
import {
|
|
12
|
+
fileExists,
|
|
13
|
+
getProjectRoot,
|
|
14
|
+
isBuenoProject,
|
|
15
|
+
joinPaths,
|
|
16
|
+
findFileUp,
|
|
17
|
+
readFile,
|
|
18
|
+
} from '../utils/fs';
|
|
19
|
+
import { CLIError, CLIErrorType } from '../index';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Find the entry point for the application
|
|
23
|
+
*/
|
|
24
|
+
async function findEntryPoint(projectRoot: string): Promise<string | null> {
|
|
25
|
+
const possibleEntries = [
|
|
26
|
+
'server/main.ts',
|
|
27
|
+
'src/main.ts',
|
|
28
|
+
'src/index.ts',
|
|
29
|
+
'main.ts',
|
|
30
|
+
'index.ts',
|
|
31
|
+
'server.ts',
|
|
32
|
+
'app.ts',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const entry of possibleEntries) {
|
|
36
|
+
const entryPath = joinPaths(projectRoot, entry);
|
|
37
|
+
if (await fileExists(entryPath)) {
|
|
38
|
+
return entry;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if package.json has dev script
|
|
47
|
+
*/
|
|
48
|
+
async function hasDevScript(projectRoot: string): Promise<boolean> {
|
|
49
|
+
const packageJsonPath = joinPaths(projectRoot, 'package.json');
|
|
50
|
+
if (!(await fileExists(packageJsonPath))) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const content = await readFile(packageJsonPath);
|
|
56
|
+
const pkg = JSON.parse(content);
|
|
57
|
+
return !!pkg.scripts?.dev;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Handle dev command
|
|
65
|
+
*/
|
|
66
|
+
async function handleDev(args: ParsedArgs): Promise<void> {
|
|
67
|
+
// Get options
|
|
68
|
+
const port = getOption(args, 'port', {
|
|
69
|
+
name: 'port',
|
|
70
|
+
alias: 'p',
|
|
71
|
+
type: 'number',
|
|
72
|
+
default: 3000,
|
|
73
|
+
description: '',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const host = getOption<string>(args, 'host', {
|
|
77
|
+
name: 'host',
|
|
78
|
+
alias: 'H',
|
|
79
|
+
type: 'string',
|
|
80
|
+
default: 'localhost',
|
|
81
|
+
description: '',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const hmr = !hasFlag(args, 'no-hmr');
|
|
85
|
+
const watch = !hasFlag(args, 'no-watch');
|
|
86
|
+
const openBrowser = hasFlag(args, 'open') || hasFlag(args, 'o');
|
|
87
|
+
const configPath = getOption<string>(args, 'config', {
|
|
88
|
+
name: 'config',
|
|
89
|
+
alias: 'c',
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: '',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Check if in a Bueno project
|
|
95
|
+
const projectRoot = await getProjectRoot();
|
|
96
|
+
if (!projectRoot) {
|
|
97
|
+
throw new CLIError(
|
|
98
|
+
'Not in a project directory. Run this command from a Bueno project.',
|
|
99
|
+
CLIErrorType.NOT_FOUND,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!(await isBuenoProject())) {
|
|
104
|
+
throw new CLIError(
|
|
105
|
+
'Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.',
|
|
106
|
+
CLIErrorType.NOT_FOUND,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Find entry point
|
|
111
|
+
const entryPoint = await findEntryPoint(projectRoot);
|
|
112
|
+
if (!entryPoint) {
|
|
113
|
+
throw new CLIError(
|
|
114
|
+
'Could not find entry point. Make sure you have a main.ts or index.ts file.',
|
|
115
|
+
CLIErrorType.FILE_NOT_FOUND,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Build the command
|
|
120
|
+
const bunArgs: string[] = [];
|
|
121
|
+
|
|
122
|
+
if (watch) {
|
|
123
|
+
bunArgs.push('--watch');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (hmr) {
|
|
127
|
+
// HMR is handled by the dev server in the framework
|
|
128
|
+
cliConsole.debug('HMR enabled');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
bunArgs.push(entryPoint);
|
|
132
|
+
|
|
133
|
+
// Set environment variables
|
|
134
|
+
const env: Record<string, string> = {
|
|
135
|
+
NODE_ENV: 'development',
|
|
136
|
+
PORT: String(port),
|
|
137
|
+
HOST: host,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (configPath) {
|
|
141
|
+
env.BUENO_CONFIG = configPath;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Display startup info
|
|
145
|
+
cliConsole.header('Starting Development Server');
|
|
146
|
+
cliConsole.log(`${colors.bold('Entry:')} ${entryPoint}`);
|
|
147
|
+
cliConsole.log(`${colors.bold('Port:')} ${port}`);
|
|
148
|
+
cliConsole.log(`${colors.bold('Host:')} ${host}`);
|
|
149
|
+
cliConsole.log(`${colors.bold('Watch:')} ${watch ? colors.green('enabled') : colors.red('disabled')}`);
|
|
150
|
+
cliConsole.log(`${colors.bold('HMR:')} ${hmr ? colors.green('enabled') : colors.red('disabled')}`);
|
|
151
|
+
cliConsole.log('');
|
|
152
|
+
|
|
153
|
+
// Start the server using Bun
|
|
154
|
+
const s = spinner('Starting development server...');
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Use Bun's spawn to run the dev server
|
|
158
|
+
const proc = Bun.spawn(['bun', 'run', ...bunArgs], {
|
|
159
|
+
cwd: projectRoot,
|
|
160
|
+
env: { ...process.env, ...env },
|
|
161
|
+
stdout: 'inherit',
|
|
162
|
+
stderr: 'inherit',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
s.success(`Development server running at ${colors.cyan(`http://${host}:${port}`)}`);
|
|
166
|
+
|
|
167
|
+
// Open browser if requested
|
|
168
|
+
if (openBrowser) {
|
|
169
|
+
const openCommand = process.platform === 'darwin'
|
|
170
|
+
? 'open'
|
|
171
|
+
: process.platform === 'win32'
|
|
172
|
+
? 'start'
|
|
173
|
+
: 'xdg-open';
|
|
174
|
+
|
|
175
|
+
Bun.spawn([openCommand, `http://${host}:${port}`], {
|
|
176
|
+
cwd: projectRoot,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Wait for the process to exit
|
|
181
|
+
const exitCode = await proc.exited;
|
|
182
|
+
|
|
183
|
+
if (exitCode !== 0 && exitCode !== null) {
|
|
184
|
+
cliConsole.error(`Server exited with code ${exitCode}`);
|
|
185
|
+
process.exit(exitCode);
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
s.error();
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Register the command
|
|
194
|
+
defineCommand(
|
|
195
|
+
{
|
|
196
|
+
name: 'dev',
|
|
197
|
+
description: 'Start the development server with hot reload',
|
|
198
|
+
options: [
|
|
199
|
+
{
|
|
200
|
+
name: 'port',
|
|
201
|
+
alias: 'p',
|
|
202
|
+
type: 'number',
|
|
203
|
+
default: 3000,
|
|
204
|
+
description: 'Server port',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'host',
|
|
208
|
+
alias: 'H',
|
|
209
|
+
type: 'string',
|
|
210
|
+
default: 'localhost',
|
|
211
|
+
description: 'Server hostname',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'no-hmr',
|
|
215
|
+
type: 'boolean',
|
|
216
|
+
default: false,
|
|
217
|
+
description: 'Disable hot module replacement',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'no-watch',
|
|
221
|
+
type: 'boolean',
|
|
222
|
+
default: false,
|
|
223
|
+
description: 'Disable file watching',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'open',
|
|
227
|
+
alias: 'o',
|
|
228
|
+
type: 'boolean',
|
|
229
|
+
default: false,
|
|
230
|
+
description: 'Open browser on start',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'config',
|
|
234
|
+
alias: 'c',
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: 'Path to config file',
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
examples: [
|
|
240
|
+
'bueno dev',
|
|
241
|
+
'bueno dev --port 4000',
|
|
242
|
+
'bueno dev --host 0.0.0.0',
|
|
243
|
+
'bueno dev --no-hmr',
|
|
244
|
+
'bueno dev --open',
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
handleDev,
|
|
248
|
+
);
|