@burger-api/cli 0.6.6

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.
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Create Command
3
+ *
4
+ * This command helps users create a new Burger API project.
5
+ * It asks simple questions and sets up everything they need to get started.
6
+ *
7
+ * We use @clack/prompts for beautiful, user-friendly interactive prompts.
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import * as clack from '@clack/prompts';
12
+ import { existsSync } from 'fs';
13
+ import { join } from 'path';
14
+ import type { CreateOptions } from '../types/index';
15
+ import { createProject, installDependencies } from '../utils/templates';
16
+ import {
17
+ success,
18
+ error as logError,
19
+ info,
20
+ newline,
21
+ header,
22
+ command,
23
+ highlight,
24
+ } from '../utils/logger';
25
+
26
+ /**
27
+ * Create the "create" command
28
+ * This is what runs when users type: burger-api create <projectName>
29
+ */
30
+ /**
31
+ * Validate project name for filesystem compatibility
32
+ * @param name - Project name to validate
33
+ * @returns Error message if invalid, undefined if valid
34
+ */
35
+ function validateProjectName(name: string): string | undefined {
36
+ if (!name) return 'Project name is required';
37
+ if (name.length > 100)
38
+ return 'Project name is too long (max 100 characters)';
39
+ if (/^[.-]/.test(name))
40
+ return 'Project name cannot start with a dot or dash';
41
+ if (/[<>:"/\\|?*\x00-\x1F]/.test(name)) {
42
+ return 'Project name contains invalid characters';
43
+ }
44
+ if (/\s/.test(name)) return 'Project name cannot contain spaces';
45
+ return undefined;
46
+ }
47
+
48
+ export const createCommand = new Command('create')
49
+ .description('Create a new Burger API project')
50
+ .argument('<project-name>', 'Name of your project')
51
+ .action(async (projectName: string) => {
52
+ // Start with a nice intro
53
+ clack.intro('Create a new BurgerAPI project');
54
+
55
+ try {
56
+ // Validate project name
57
+ const nameError = validateProjectName(projectName);
58
+ if (nameError) {
59
+ clack.outro('Invalid project name');
60
+ logError(nameError);
61
+ process.exit(1);
62
+ }
63
+
64
+ // Check if directory already exists
65
+ const targetDir = join(process.cwd(), projectName);
66
+ if (existsSync(targetDir)) {
67
+ clack.outro('Directory already exists!');
68
+ logError(`A directory named "${projectName}" already exists.`);
69
+ process.exit(1);
70
+ }
71
+
72
+ // Ask user questions to configure the project
73
+ const options = await askQuestions(projectName);
74
+
75
+ // User cancelled
76
+ if (clack.isCancel(options)) {
77
+ clack.outro('Operation cancelled');
78
+ process.exit(0);
79
+ }
80
+
81
+ // Show what we're about to create
82
+ info('Creating project with the following configuration:');
83
+ newline();
84
+ console.log(` Name: ${projectName}`);
85
+ if (options.useApi) {
86
+ console.log(` API Routes: ${options.apiDir || 'api'}`);
87
+ }
88
+ if (options.usePages) {
89
+ console.log(` Page Routes: ${options.pageDir || 'pages'}`);
90
+ }
91
+ newline();
92
+
93
+ // Create the project
94
+ await createProject(targetDir, options);
95
+
96
+ // Install dependencies
97
+ await installDependencies(targetDir);
98
+
99
+ // Success! Show them what to do next
100
+ clack.outro('Project created successfully!');
101
+ newline();
102
+ header('Next Steps');
103
+ console.log(` 1. Navigate to your project:`);
104
+ command(`cd ${projectName}`);
105
+ newline();
106
+ console.log(` 2. Start the development server:`);
107
+ command('bun run dev');
108
+ newline();
109
+ console.log(` 3. Open your browser:`);
110
+ console.log(` ${highlight('http://localhost:4000')}`);
111
+ newline();
112
+ console.log(` 4. Add middleware (optional):`);
113
+ command('burger-api add cors logger');
114
+ newline();
115
+ success('Happy coding!');
116
+ } catch (err) {
117
+ clack.outro('Failed to create project');
118
+ logError(err instanceof Error ? err.message : 'Unknown error');
119
+ process.exit(1);
120
+ }
121
+ });
122
+
123
+ /**
124
+ * Ask user questions to configure their project
125
+ * Uses @clack/prompts for beautiful interactive prompts
126
+ *
127
+ * @param projectName - Name of the project
128
+ * @returns Configuration options from user answers
129
+ */
130
+ async function askQuestions(projectName: string): Promise<CreateOptions> {
131
+ // Ask all questions in a nice flow
132
+ const answers = await clack.group(
133
+ {
134
+ // Question 1: Do you need API routes?
135
+ useApi: () =>
136
+ clack.confirm({
137
+ message: 'Do you need API routes?',
138
+ initialValue: true,
139
+ }),
140
+
141
+ // Question 2: API directory (only if they said yes to API)
142
+ apiDir: ({ results }) =>
143
+ results.useApi
144
+ ? clack.text({
145
+ message: 'API directory name:',
146
+ initialValue: 'api',
147
+ placeholder: 'api',
148
+ validate: (value) => {
149
+ if (!value)
150
+ return 'Please enter a directory name';
151
+ if (value.includes(' '))
152
+ return 'Directory name cannot contain spaces';
153
+ },
154
+ })
155
+ : Promise.resolve('api'),
156
+
157
+ // Question 3: API prefix (only if they said yes to API)
158
+ apiPrefix: ({ results }) =>
159
+ results.useApi
160
+ ? clack.text({
161
+ message: 'API route prefix:',
162
+ initialValue: '/api',
163
+ placeholder: '/api',
164
+ })
165
+ : Promise.resolve('/api'),
166
+
167
+ // Question 4: Debug mode (only if they said yes to API)
168
+ debug: ({ results }) =>
169
+ results.useApi
170
+ ? clack.confirm({
171
+ message: 'Enable debug mode?',
172
+ initialValue: false,
173
+ })
174
+ : Promise.resolve(false),
175
+
176
+ // Question 5: Do you need Page routes?
177
+ usePages: () =>
178
+ clack.confirm({
179
+ message: 'Do you need Page routes?',
180
+ initialValue: false,
181
+ }),
182
+
183
+ // Question 6: Page directory (only if they said yes to Pages)
184
+ pageDir: ({ results }) =>
185
+ results.usePages
186
+ ? clack.text({
187
+ message: 'Page directory name:',
188
+ initialValue: 'pages',
189
+ placeholder: 'pages',
190
+ validate: (value) => {
191
+ if (!value)
192
+ return 'Please enter a directory name';
193
+ if (value.includes(' '))
194
+ return 'Directory name cannot contain spaces';
195
+ },
196
+ })
197
+ : Promise.resolve('pages'),
198
+
199
+ // Question 7: Page prefix (only if they said yes to Pages)
200
+ pagePrefix: ({ results }) =>
201
+ results.usePages
202
+ ? clack.text({
203
+ message: 'Page route prefix:',
204
+ initialValue: '/',
205
+ placeholder: '/',
206
+ })
207
+ : Promise.resolve('/'),
208
+ },
209
+ {
210
+ // Callback when user cancels (Ctrl+C)
211
+ onCancel: () => {
212
+ clack.cancel('Operation cancelled');
213
+ process.exit(0);
214
+ },
215
+ }
216
+ );
217
+
218
+ // Return the configuration
219
+ return {
220
+ name: projectName,
221
+ useApi: answers.useApi as boolean,
222
+ apiDir: answers.apiDir as string | undefined,
223
+ apiPrefix: answers.apiPrefix as string | undefined,
224
+ debug: answers.debug as boolean | undefined,
225
+ usePages: answers.usePages as boolean,
226
+ pageDir: answers.pageDir as string | undefined,
227
+ pagePrefix: answers.pagePrefix as string | undefined,
228
+ };
229
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * List Command
3
+ *
4
+ * Shows users all available middleware they can add to their project.
5
+ * Fetches the list from GitHub and displays it in a nice table format.
6
+ *
7
+ * Example: burger-api list
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import { getMiddlewareList, getMiddlewareInfo } from '../utils/github';
12
+ import {
13
+ header,
14
+ spinner,
15
+ error as logError,
16
+ table,
17
+ newline,
18
+ info,
19
+ dim,
20
+ command,
21
+ } from '../utils/logger';
22
+
23
+ /**
24
+ * Create the "list" command
25
+ * This shows all available middleware from the ecosystem
26
+ */
27
+ export const listCommand = new Command('list')
28
+ .description('Show available middleware from the ecosystem')
29
+ .alias('ls') // Allow users to type "burger-api ls" too
30
+ .action(async () => {
31
+ // Show a spinner while fetching from GitHub
32
+ const spin = spinner('Fetching middleware list from GitHub...');
33
+
34
+ try {
35
+ // Get the list of middleware names
36
+ const middlewareNames = await getMiddlewareList();
37
+
38
+ // Fetch details for each middleware (in parallel for speed!)
39
+ const middlewareDetails = await Promise.all(
40
+ middlewareNames.map((name) =>
41
+ getMiddlewareInfo(name).catch(() => ({
42
+ name,
43
+ description: 'No description available',
44
+ path: '',
45
+ files: [],
46
+ }))
47
+ )
48
+ );
49
+
50
+ spin.stop('Found available middleware!');
51
+ newline();
52
+
53
+ // Display header
54
+ header('Available Middleware');
55
+
56
+ // Create table data
57
+ const tableData: string[][] = [
58
+ ['Name', 'Description'],
59
+ ...middlewareDetails.map((m) => [
60
+ m.name,
61
+ m.description.length > 60
62
+ ? m.description.substring(0, 57) + '...'
63
+ : m.description,
64
+ ]),
65
+ ];
66
+
67
+ // Show the table
68
+ table(tableData);
69
+ newline();
70
+
71
+ // Show usage instructions
72
+ info('To add middleware to your project, run:');
73
+ command('burger-api add <middleware-name>');
74
+ newline();
75
+ dim('Example: burger-api add cors logger rate-limiter');
76
+ newline();
77
+ } catch (err) {
78
+ spin.stop('Failed to fetch middleware list', true);
79
+ logError(
80
+ err instanceof Error
81
+ ? err.message
82
+ : 'Could not connect to GitHub'
83
+ );
84
+ newline();
85
+ info('Please check your internet connection and try again.');
86
+ process.exit(1);
87
+ }
88
+ });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Serve Command
3
+ *
4
+ * Runs a development server with hot reload (auto-restart on file changes).
5
+ * This is perfect for development - just edit your code and see changes instantly!
6
+ *
7
+ * Example: burger-api serve
8
+ * Example: burger-api serve --port 4000
9
+ */
10
+
11
+ import { Command } from 'commander';
12
+ import { existsSync } from 'fs';
13
+ import {
14
+ success,
15
+ error as logError,
16
+ info,
17
+ newline,
18
+ highlight,
19
+ dim,
20
+ } from '../utils/logger';
21
+
22
+ /**
23
+ * Serve command options
24
+ */
25
+ interface ServeCommandOptions {
26
+ port: string;
27
+ file: string;
28
+ }
29
+
30
+ /**
31
+ * Create the "serve" command
32
+ * Starts a development server with hot reload
33
+ */
34
+ export const serveCommand = new Command('serve')
35
+ .description('Start development server with hot reload')
36
+ .option('-p, --port <port>', 'Port to run the server on', '4000')
37
+ .option('-f, --file <file>', 'Entry file to run', 'src/index.ts')
38
+ .action(async (options: ServeCommandOptions) => {
39
+ const file = options.file;
40
+ const port = options.port;
41
+
42
+ // Check if the entry file exists
43
+ if (!existsSync(file)) {
44
+ logError(`Entry file not found: ${file}`);
45
+ info('Make sure you are in the project directory.');
46
+ process.exit(1);
47
+ }
48
+
49
+ // Show startup message
50
+ newline();
51
+ info('Starting development server...');
52
+ newline();
53
+ success(`Server running on ${highlight(`http://localhost:${port}`)}`);
54
+ info('Press Ctrl+C to stop');
55
+ dim('File changes will automatically restart the server');
56
+ newline();
57
+
58
+ try {
59
+ // Run bun with --watch flag for hot reload
60
+ // We use --watch to automatically restart when files change
61
+ const proc = Bun.spawn(['bun', '--watch', file], {
62
+ stdout: 'inherit', // Show output in the terminal
63
+ stderr: 'inherit', // Show errors in the terminal
64
+ stdin: 'inherit', // Allow user input
65
+ env: {
66
+ ...process.env,
67
+ PORT: port, // Pass port as environment variable
68
+ },
69
+ });
70
+
71
+ // Handle Ctrl+C gracefully
72
+ process.on('SIGINT', () => {
73
+ newline();
74
+ info('Shutting down server...');
75
+ proc.kill();
76
+ process.exit(0);
77
+ });
78
+
79
+ // Handle Ctrl+Break on Windows
80
+ process.on('SIGBREAK', () => {
81
+ newline();
82
+ info('Shutting down server...');
83
+ proc.kill();
84
+ process.exit(0);
85
+ });
86
+
87
+ // Wait for the process to exit
88
+ const exitCode = await proc.exited;
89
+
90
+ if (exitCode !== 0) {
91
+ logError('Server stopped unexpectedly');
92
+ process.exit(exitCode);
93
+ }
94
+ } catch (err) {
95
+ logError(
96
+ err instanceof Error ? err.message : 'Failed to start server'
97
+ );
98
+ process.exit(1);
99
+ }
100
+ });
package/src/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * BurgerAPI CLI Tool
5
+ *
6
+ * This is the main entry point for the CLI.
7
+ * It sets up all the commands users can run.
8
+ *
9
+ * We use commander to handle command parsing and routing.
10
+ * This makes it easy to add new commands and provide helpful error messages.
11
+ */
12
+
13
+ import { Command } from 'commander';
14
+ import { createCommand } from './commands/create';
15
+ import { addCommand } from './commands/add';
16
+ import { listCommand } from './commands/list';
17
+ import { buildCommand, buildExecutableCommand } from './commands/build';
18
+ import { serveCommand } from './commands/serve';
19
+ import { showBanner } from './utils/logger';
20
+
21
+ /**
22
+ * Create the main CLI program
23
+ * This is what runs when someone types 'burger-api' in their terminal
24
+ */
25
+ const program = new Command();
26
+
27
+ // Set up basic information about our CLI
28
+ program
29
+ .name('burger-api')
30
+ .description('Simple tool to work with BurgerAPI projects')
31
+ .version('0.6.6');
32
+
33
+ // Add all our commands to the CLI
34
+ // Each command is defined in its own file for better organization
35
+ program.addCommand(createCommand); // Create new projects
36
+ program.addCommand(addCommand); // Add middleware to projects
37
+ program.addCommand(listCommand); // List available middleware
38
+ program.addCommand(buildCommand); // Bundle to JS file
39
+ program.addCommand(buildExecutableCommand); // Compile to executable
40
+ program.addCommand(serveCommand); // Run development server
41
+
42
+ // Show banner + help when no command is provided
43
+ program.action(() => {
44
+ showBanner();
45
+ program.help();
46
+ });
47
+
48
+ // Override the help display to include banner
49
+ program.configureOutput({
50
+ writeOut: (str) => {
51
+ process.stdout.write(str);
52
+ },
53
+ writeErr: (str) => {
54
+ process.stderr.write(str);
55
+ },
56
+ });
57
+
58
+ // Run the CLI - this parses the arguments the user typed
59
+ program.parse();
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Shared TypeScript types for the CLI
3
+ *
4
+ * These types are used across different parts of the CLI
5
+ * to ensure type safety and good developer experience.
6
+ */
7
+
8
+ /**
9
+ * Options for creating a new Burger API project
10
+ */
11
+ export interface CreateOptions {
12
+ /** Name of the project */
13
+ name: string;
14
+ /** Whether to include API routes */
15
+ useApi: boolean;
16
+ /** Directory for API routes (e.g., 'api') */
17
+ apiDir?: string;
18
+ /** Prefix for API routes (e.g., '/api') */
19
+ apiPrefix?: string;
20
+ /** Enable debug mode */
21
+ debug?: boolean;
22
+ /** Whether to include Page routes */
23
+ usePages: boolean;
24
+ /** Directory for Page routes (e.g., 'pages') */
25
+ pageDir?: string;
26
+ /** Prefix for Page routes (e.g., '/') */
27
+ pagePrefix?: string;
28
+ }
29
+
30
+ /**
31
+ * Information about a middleware/feature from GitHub
32
+ */
33
+ export interface MiddlewareInfo {
34
+ /** Name of the middleware (e.g., 'cors') */
35
+ name: string;
36
+ /** Short description of what it does */
37
+ description: string;
38
+ /** Path in the GitHub repo */
39
+ path: string;
40
+ /** Files that are part of this middleware */
41
+ files: string[];
42
+ }
43
+
44
+ /**
45
+ * GitHub API response for directory contents
46
+ */
47
+ export interface GitHubFile {
48
+ name: string;
49
+ path: string;
50
+ type: 'file' | 'dir';
51
+ download_url?: string;
52
+ size: number;
53
+ }