@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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start Command
|
|
3
|
+
*
|
|
4
|
+
* Start the production server
|
|
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
|
+
} from '../utils/fs';
|
|
17
|
+
import { CLIError, CLIErrorType } from '../index';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Find the entry point for the application
|
|
21
|
+
*/
|
|
22
|
+
async function findEntryPoint(projectRoot: string): Promise<string | null> {
|
|
23
|
+
// Check for built files first
|
|
24
|
+
const possibleBuiltEntries = [
|
|
25
|
+
'dist/index.js',
|
|
26
|
+
'dist/main.js',
|
|
27
|
+
'dist/server.js',
|
|
28
|
+
'dist/app.js',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const entry of possibleBuiltEntries) {
|
|
32
|
+
const entryPath = joinPaths(projectRoot, entry);
|
|
33
|
+
if (await fileExists(entryPath)) {
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fall back to source files
|
|
39
|
+
const possibleSourceEntries = [
|
|
40
|
+
'server/main.ts',
|
|
41
|
+
'src/main.ts',
|
|
42
|
+
'src/index.ts',
|
|
43
|
+
'main.ts',
|
|
44
|
+
'index.ts',
|
|
45
|
+
'server.ts',
|
|
46
|
+
'app.ts',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
for (const entry of possibleSourceEntries) {
|
|
50
|
+
const entryPath = joinPaths(projectRoot, entry);
|
|
51
|
+
if (await fileExists(entryPath)) {
|
|
52
|
+
return entry;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handle start command
|
|
61
|
+
*/
|
|
62
|
+
async function handleStart(args: ParsedArgs): Promise<void> {
|
|
63
|
+
// Get options
|
|
64
|
+
const port = getOption(args, 'port', {
|
|
65
|
+
name: 'port',
|
|
66
|
+
alias: 'p',
|
|
67
|
+
type: 'number',
|
|
68
|
+
default: 3000,
|
|
69
|
+
description: '',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const host = getOption<string>(args, 'host', {
|
|
73
|
+
name: 'host',
|
|
74
|
+
alias: 'H',
|
|
75
|
+
type: 'string',
|
|
76
|
+
default: '0.0.0.0',
|
|
77
|
+
description: '',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const workers = getOption(args, 'workers', {
|
|
81
|
+
name: 'workers',
|
|
82
|
+
alias: 'w',
|
|
83
|
+
type: 'string',
|
|
84
|
+
default: 'auto',
|
|
85
|
+
description: '',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const configPath = getOption<string>(args, 'config', {
|
|
89
|
+
name: 'config',
|
|
90
|
+
alias: 'c',
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: '',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Check if in a Bueno project
|
|
96
|
+
const projectRoot = await getProjectRoot();
|
|
97
|
+
if (!projectRoot) {
|
|
98
|
+
throw new CLIError(
|
|
99
|
+
'Not in a project directory. Run this command from a Bueno project.',
|
|
100
|
+
CLIErrorType.NOT_FOUND,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!(await isBuenoProject())) {
|
|
105
|
+
throw new CLIError(
|
|
106
|
+
'Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.',
|
|
107
|
+
CLIErrorType.NOT_FOUND,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Find entry point
|
|
112
|
+
const entryPoint = await findEntryPoint(projectRoot);
|
|
113
|
+
if (!entryPoint) {
|
|
114
|
+
throw new CLIError(
|
|
115
|
+
'Could not find entry point. Make sure you have built the application or have a main.ts file.',
|
|
116
|
+
CLIErrorType.FILE_NOT_FOUND,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Display startup info
|
|
121
|
+
cliConsole.header('Starting Production Server');
|
|
122
|
+
cliConsole.log(`${colors.bold('Entry:')} ${entryPoint}`);
|
|
123
|
+
cliConsole.log(`${colors.bold('Port:')} ${port}`);
|
|
124
|
+
cliConsole.log(`${colors.bold('Host:')} ${host}`);
|
|
125
|
+
cliConsole.log(`${colors.bold('Workers:')} ${workers}`);
|
|
126
|
+
cliConsole.log('');
|
|
127
|
+
|
|
128
|
+
// Set environment variables
|
|
129
|
+
const env: Record<string, string> = {
|
|
130
|
+
NODE_ENV: 'production',
|
|
131
|
+
PORT: String(port),
|
|
132
|
+
HOST: host,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (configPath) {
|
|
136
|
+
env.BUENO_CONFIG = configPath;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Start the server using Bun
|
|
140
|
+
const s = spinner('Starting production server...');
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// Use Bun's spawn to run the production server
|
|
144
|
+
const proc = Bun.spawn(['bun', 'run', entryPoint], {
|
|
145
|
+
cwd: projectRoot,
|
|
146
|
+
env: { ...process.env, ...env },
|
|
147
|
+
stdout: 'inherit',
|
|
148
|
+
stderr: 'inherit',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
s.success(`Production server running at ${colors.cyan(`http://${host}:${port}`)}`);
|
|
152
|
+
|
|
153
|
+
// Wait for the process to exit
|
|
154
|
+
const exitCode = await proc.exited;
|
|
155
|
+
|
|
156
|
+
if (exitCode !== 0 && exitCode !== null) {
|
|
157
|
+
cliConsole.error(`Server exited with code ${exitCode}`);
|
|
158
|
+
process.exit(exitCode);
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
s.error();
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Register the command
|
|
167
|
+
defineCommand(
|
|
168
|
+
{
|
|
169
|
+
name: 'start',
|
|
170
|
+
description: 'Start the production server',
|
|
171
|
+
options: [
|
|
172
|
+
{
|
|
173
|
+
name: 'port',
|
|
174
|
+
alias: 'p',
|
|
175
|
+
type: 'number',
|
|
176
|
+
default: 3000,
|
|
177
|
+
description: 'Server port',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'host',
|
|
181
|
+
alias: 'H',
|
|
182
|
+
type: 'string',
|
|
183
|
+
default: '0.0.0.0',
|
|
184
|
+
description: 'Server hostname',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'workers',
|
|
188
|
+
alias: 'w',
|
|
189
|
+
type: 'string',
|
|
190
|
+
default: 'auto',
|
|
191
|
+
description: 'Number of worker threads',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'config',
|
|
195
|
+
alias: 'c',
|
|
196
|
+
type: 'string',
|
|
197
|
+
description: 'Path to config file',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
examples: [
|
|
201
|
+
'bueno start',
|
|
202
|
+
'bueno start --port 8080',
|
|
203
|
+
'bueno start --host 0.0.0.0',
|
|
204
|
+
'bueno start --workers 4',
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
handleStart,
|
|
208
|
+
);
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument Parser for Bueno CLI
|
|
3
|
+
*
|
|
4
|
+
* Parses command line arguments using Bun's native process.argv
|
|
5
|
+
* Supports positional arguments, flags, and options
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ParsedArgs {
|
|
9
|
+
command: string;
|
|
10
|
+
positionals: string[];
|
|
11
|
+
options: Record<string, string | boolean | number>;
|
|
12
|
+
flags: Set<string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OptionDefinition {
|
|
16
|
+
name: string;
|
|
17
|
+
alias?: string;
|
|
18
|
+
type: 'string' | 'boolean' | 'number';
|
|
19
|
+
default?: string | boolean | number;
|
|
20
|
+
description: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CommandDefinition {
|
|
24
|
+
name: string;
|
|
25
|
+
alias?: string;
|
|
26
|
+
description: string;
|
|
27
|
+
options?: OptionDefinition[];
|
|
28
|
+
positionals?: { name: string; required: boolean; description: string }[];
|
|
29
|
+
examples?: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse command line arguments
|
|
34
|
+
*/
|
|
35
|
+
export function parseArgs(
|
|
36
|
+
argv: string[] = process.argv.slice(2),
|
|
37
|
+
): ParsedArgs {
|
|
38
|
+
const result: ParsedArgs = {
|
|
39
|
+
command: '',
|
|
40
|
+
positionals: [],
|
|
41
|
+
options: {},
|
|
42
|
+
flags: new Set(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < argv.length; i++) {
|
|
46
|
+
const arg = argv[i];
|
|
47
|
+
|
|
48
|
+
if (!arg) continue;
|
|
49
|
+
|
|
50
|
+
// Long option: --option=value or --option value
|
|
51
|
+
if (arg.startsWith('--')) {
|
|
52
|
+
const eqIndex = arg.indexOf('=');
|
|
53
|
+
if (eqIndex !== -1) {
|
|
54
|
+
// --option=value
|
|
55
|
+
const name = arg.slice(2, eqIndex);
|
|
56
|
+
const value = arg.slice(eqIndex + 1);
|
|
57
|
+
result.options[name] = value;
|
|
58
|
+
} else {
|
|
59
|
+
// --option value or --flag
|
|
60
|
+
const name = arg.slice(2);
|
|
61
|
+
const nextArg = argv[i + 1];
|
|
62
|
+
|
|
63
|
+
// Check if it's a flag (no value or next arg starts with -)
|
|
64
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
65
|
+
result.options[name] = true;
|
|
66
|
+
result.flags.add(name);
|
|
67
|
+
} else {
|
|
68
|
+
result.options[name] = nextArg;
|
|
69
|
+
i++; // Skip next arg as it's the value
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Short option: -o value or -abc (multiple flags)
|
|
74
|
+
else if (arg.startsWith('-') && arg.length > 1) {
|
|
75
|
+
const chars = arg.slice(1);
|
|
76
|
+
|
|
77
|
+
// Check if it's a combined flag like -abc
|
|
78
|
+
if (chars.length > 1) {
|
|
79
|
+
// Treat each char as a flag
|
|
80
|
+
for (const char of chars) {
|
|
81
|
+
result.options[char] = true;
|
|
82
|
+
result.flags.add(char);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
// Single short option
|
|
86
|
+
const name = chars;
|
|
87
|
+
const nextArg = argv[i + 1];
|
|
88
|
+
|
|
89
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
90
|
+
result.options[name] = true;
|
|
91
|
+
result.flags.add(name);
|
|
92
|
+
} else {
|
|
93
|
+
result.options[name] = nextArg;
|
|
94
|
+
i++; // Skip next arg as it's the value
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Positional argument
|
|
99
|
+
else {
|
|
100
|
+
if (!result.command) {
|
|
101
|
+
result.command = arg;
|
|
102
|
+
} else {
|
|
103
|
+
result.positionals.push(arg);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get option value with type coercion and default
|
|
113
|
+
*/
|
|
114
|
+
export function getOption<T extends string | boolean | number>(
|
|
115
|
+
parsed: ParsedArgs,
|
|
116
|
+
name: string,
|
|
117
|
+
definition: OptionDefinition,
|
|
118
|
+
): T {
|
|
119
|
+
const value = parsed.options[name] ?? parsed.options[definition.alias ?? ''];
|
|
120
|
+
|
|
121
|
+
if (value === undefined) {
|
|
122
|
+
return definition.default as T;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (definition.type === 'boolean') {
|
|
126
|
+
return (value === true || value === 'true') as T;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (definition.type === 'number') {
|
|
130
|
+
return (typeof value === 'number' ? value : typeof value === 'string' ? parseInt(value, 10) : NaN) as T;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return value as T;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if a flag is set
|
|
138
|
+
*/
|
|
139
|
+
export function hasFlag(parsed: ParsedArgs, name: string, alias?: string): boolean {
|
|
140
|
+
return parsed.flags.has(name) || (alias ? parsed.flags.has(alias) : false);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if an option is set (either as flag or with value)
|
|
145
|
+
*/
|
|
146
|
+
export function hasOption(parsed: ParsedArgs, name: string, alias?: string): boolean {
|
|
147
|
+
return name in parsed.options || (alias ? alias in parsed.options : false);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all values for an option that can be specified multiple times
|
|
152
|
+
* Parses raw argv to collect all occurrences of the option
|
|
153
|
+
*/
|
|
154
|
+
export function getOptionValues(parsed: ParsedArgs, name: string, alias?: string): string[] {
|
|
155
|
+
const values: string[] = [];
|
|
156
|
+
const argv = process.argv.slice(2);
|
|
157
|
+
|
|
158
|
+
for (let i = 0; i < argv.length; i++) {
|
|
159
|
+
const arg = argv[i];
|
|
160
|
+
if (!arg) continue;
|
|
161
|
+
|
|
162
|
+
// Long option: --name value or --name=value
|
|
163
|
+
if (arg === `--${name}`) {
|
|
164
|
+
const nextArg = argv[i + 1];
|
|
165
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
166
|
+
values.push(nextArg);
|
|
167
|
+
i++; // Skip next arg
|
|
168
|
+
}
|
|
169
|
+
} else if (arg.startsWith(`--${name}=`)) {
|
|
170
|
+
const value = arg.slice(name.length + 3); // --name=
|
|
171
|
+
values.push(value);
|
|
172
|
+
}
|
|
173
|
+
// Short option: -n value
|
|
174
|
+
else if (alias && arg === `-${alias}`) {
|
|
175
|
+
const nextArg = argv[i + 1];
|
|
176
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
177
|
+
values.push(nextArg);
|
|
178
|
+
i++; // Skip next arg
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return values;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate help text from command definition
|
|
188
|
+
*/
|
|
189
|
+
export function generateHelpText(
|
|
190
|
+
command: CommandDefinition,
|
|
191
|
+
cliName = 'bueno',
|
|
192
|
+
): string {
|
|
193
|
+
const lines: string[] = [];
|
|
194
|
+
|
|
195
|
+
// Description
|
|
196
|
+
lines.push(`\n${command.description}\n`);
|
|
197
|
+
|
|
198
|
+
// Usage
|
|
199
|
+
lines.push('Usage:');
|
|
200
|
+
let usage = ` ${cliName} ${command.name}`;
|
|
201
|
+
|
|
202
|
+
if (command.positionals) {
|
|
203
|
+
for (const pos of command.positionals) {
|
|
204
|
+
usage += pos.required ? ` <${pos.name}>` : ` [${pos.name}]`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
usage += ' [options]';
|
|
209
|
+
lines.push(usage + '\n');
|
|
210
|
+
|
|
211
|
+
// Positionals
|
|
212
|
+
if (command.positionals && command.positionals.length > 0) {
|
|
213
|
+
lines.push('Arguments:');
|
|
214
|
+
for (const pos of command.positionals) {
|
|
215
|
+
const required = pos.required ? ' (required)' : '';
|
|
216
|
+
lines.push(` ${pos.name.padEnd(20)} ${pos.description}${required}`);
|
|
217
|
+
}
|
|
218
|
+
lines.push('');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Options
|
|
222
|
+
if (command.options && command.options.length > 0) {
|
|
223
|
+
lines.push('Options:');
|
|
224
|
+
for (const opt of command.options) {
|
|
225
|
+
let flag = `--${opt.name}`;
|
|
226
|
+
if (opt.alias) {
|
|
227
|
+
flag = `-${opt.alias}, ${flag}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let defaultValue = '';
|
|
231
|
+
if (opt.default !== undefined) {
|
|
232
|
+
defaultValue = ` (default: ${opt.default})`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
lines.push(` ${flag.padEnd(20)} ${opt.description}${defaultValue}`);
|
|
236
|
+
}
|
|
237
|
+
lines.push('');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Examples
|
|
241
|
+
if (command.examples && command.examples.length > 0) {
|
|
242
|
+
lines.push('Examples:');
|
|
243
|
+
for (const example of command.examples) {
|
|
244
|
+
lines.push(` ${example}`);
|
|
245
|
+
}
|
|
246
|
+
lines.push('');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Generate global help text
|
|
254
|
+
*/
|
|
255
|
+
export function generateGlobalHelpText(
|
|
256
|
+
commands: CommandDefinition[],
|
|
257
|
+
cliName = 'bueno',
|
|
258
|
+
): string {
|
|
259
|
+
const lines: string[] = [];
|
|
260
|
+
|
|
261
|
+
lines.push(`\n${cliName} - A Bun-Native Full-Stack Framework CLI\n`);
|
|
262
|
+
lines.push('Usage:');
|
|
263
|
+
lines.push(` ${cliName} <command> [options]\n`);
|
|
264
|
+
|
|
265
|
+
lines.push('Commands:');
|
|
266
|
+
for (const cmd of commands) {
|
|
267
|
+
const name = cmd.alias ? `${cmd.name} (${cmd.alias})` : cmd.name;
|
|
268
|
+
lines.push(` ${name.padEnd(20)} ${cmd.description}`);
|
|
269
|
+
}
|
|
270
|
+
lines.push('');
|
|
271
|
+
|
|
272
|
+
lines.push('Global Options:');
|
|
273
|
+
lines.push(' --help, -h Show help for command');
|
|
274
|
+
lines.push(' --version, -v Show CLI version');
|
|
275
|
+
lines.push(' --verbose Enable verbose output');
|
|
276
|
+
lines.push(' --quiet Suppress non-essential output');
|
|
277
|
+
lines.push(' --no-color Disable colored output');
|
|
278
|
+
lines.push('');
|
|
279
|
+
|
|
280
|
+
lines.push(`Run '${cliName} <command> --help' for more information about a command.\n`);
|
|
281
|
+
|
|
282
|
+
return lines.join('\n');
|
|
283
|
+
}
|