@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.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@burger-api/cli",
3
+ "version": "0.6.6",
4
+ "description": "Simple command-line tool for Burger API projects",
5
+ "module": "src/index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "burger-api": "./src/index.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "CHANGELOG.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "bun": ">=1.3.0"
18
+ },
19
+ "scripts": {
20
+ "dev": "bun run src/index.ts",
21
+ "build:win": "bun build ./src/index.ts --compile --target bun-windows-x64 --outfile dist/burger-api.exe --minify",
22
+ "build:linux": "bun build ./src/index.ts --compile --target bun-linux-x64 --outfile dist/burger-api-linux --minify",
23
+ "build:mac": "bun build ./src/index.ts --compile --target bun-darwin-arm64 --outfile dist/burger-api-mac --minify",
24
+ "build:mac-intel": "bun build ./src/index.ts --compile --target bun-darwin-x64 --outfile dist/burger-api-mac-intel --minify",
25
+ "build:all": "bun run build:win && bun run build:linux && bun run build:mac && bun run build:mac-intel"
26
+ },
27
+ "dependencies": {
28
+ "commander": "^11.1.0",
29
+ "@clack/prompts": "^0.7.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/bun": "latest",
33
+ "@types/commander": "^2.12.5"
34
+ },
35
+ "peerDependencies": {
36
+ "typescript": "^5"
37
+ },
38
+ "keywords": [
39
+ "burger-api",
40
+ "cli",
41
+ "framework",
42
+ "bun"
43
+ ],
44
+ "author": "Isfhan Ahmed",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/isfhan/burger-api.git",
49
+ "directory": "packages/cli"
50
+ }
51
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Add Command
3
+ *
4
+ * Downloads middleware from the ecosystem and adds it to the user's project.
5
+ * Users can add multiple middleware at once!
6
+ *
7
+ * Example: burger-api add cors logger rate-limiter
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import { existsSync } from 'fs';
12
+ import { join } from 'path';
13
+ import * as clack from '@clack/prompts';
14
+ import { generateMiddlewareIndex } from '../utils/templates';
15
+ import { middlewareExists, downloadMiddleware } from '../utils/github';
16
+ import {
17
+ spinner,
18
+ success,
19
+ error as logError,
20
+ info,
21
+ newline,
22
+ header,
23
+ code,
24
+ warning,
25
+ bullet,
26
+ } from '../utils/logger';
27
+
28
+ /**
29
+ * Create the "add" command
30
+ * Downloads middleware from GitHub and copies to project
31
+ */
32
+ export const addCommand = new Command('add')
33
+ .description('Add middleware from the ecosystem')
34
+ .argument('<middleware...>', 'Names of middleware to add')
35
+ .action(async (middlewareNames: string[]) => {
36
+ clack.intro('Add middleware to your project');
37
+
38
+ // Make sure we're in a BurgerAPI project
39
+ if (!existsSync('package.json')) {
40
+ clack.outro('Not in a BurgerAPI project');
41
+ logError(
42
+ 'Please run this command from a BurgerAPI project directory.'
43
+ );
44
+ info('Create a new project with: burger-api create <name>');
45
+ process.exit(1);
46
+ }
47
+
48
+ // Create ecosystem/middleware directory if it doesn't exist
49
+ // Ecosystem middleware goes here, user's custom middleware can go in middleware/
50
+ const ecosystemDir = join(process.cwd(), 'ecosystem');
51
+ const middlewareDir = join(ecosystemDir, 'middleware');
52
+ if (!existsSync(middlewareDir)) {
53
+ // Create it with a proper starter file
54
+ await Bun.write(
55
+ join(middlewareDir, 'index.ts'),
56
+ generateMiddlewareIndex()
57
+ );
58
+ info('Created ecosystem/middleware/ directory');
59
+ newline();
60
+ }
61
+
62
+ // Process each middleware
63
+ const results = {
64
+ success: [] as string[],
65
+ failed: [] as string[],
66
+ skipped: [] as string[],
67
+ };
68
+
69
+ for (const name of middlewareNames) {
70
+ try {
71
+ // Check if it exists on GitHub
72
+ const spin = spinner(`Checking ${name}...`);
73
+
74
+ let exists;
75
+ try {
76
+ exists = await middlewareExists(name);
77
+ } catch (err) {
78
+ spin.stop('Could not connect to GitHub', true);
79
+ logError(
80
+ 'Please check your internet connection and try again.'
81
+ );
82
+ results.failed.push(name);
83
+ continue;
84
+ }
85
+
86
+ if (!exists) {
87
+ spin.stop(`Middleware "${name}" not found`, true);
88
+ results.failed.push(name);
89
+ continue;
90
+ }
91
+
92
+ spin.update(`Downloading ${name}...`);
93
+
94
+ // Check if it already exists locally
95
+ const targetDir = join(middlewareDir, name);
96
+ if (existsSync(targetDir)) {
97
+ spin.stop();
98
+ // Ask if they want to overwrite
99
+ const shouldOverwrite = await clack.confirm({
100
+ message: `${name} already exists. Overwrite?`,
101
+ initialValue: false,
102
+ });
103
+
104
+ if (clack.isCancel(shouldOverwrite) || !shouldOverwrite) {
105
+ info(`Skipped ${name}`);
106
+ results.skipped.push(name);
107
+ continue;
108
+ }
109
+ }
110
+
111
+ // Download the middleware
112
+ try {
113
+ const filesDownloaded = await downloadMiddleware(
114
+ name,
115
+ targetDir
116
+ );
117
+ spin.stop(`Added ${name} (${filesDownloaded} files)`);
118
+ results.success.push(name);
119
+ } catch (err) {
120
+ spin.stop('Download failed', true);
121
+ if (
122
+ err instanceof Error &&
123
+ err.message.includes('Could not download')
124
+ ) {
125
+ logError(
126
+ 'Please check your internet connection and try again.'
127
+ );
128
+ } else {
129
+ logError(
130
+ err instanceof Error ? err.message : 'Unknown error'
131
+ );
132
+ }
133
+ results.failed.push(name);
134
+ }
135
+ } catch (err) {
136
+ logError(
137
+ `Failed to add ${name}: ${
138
+ err instanceof Error ? err.message : 'Unknown error'
139
+ }`
140
+ );
141
+ results.failed.push(name);
142
+ }
143
+ }
144
+
145
+ // Show summary
146
+ newline();
147
+ if (results.success.length > 0) {
148
+ success(`Successfully added ${results.success.length} middleware:`);
149
+ results.success.forEach((name) => bullet(name));
150
+ newline();
151
+
152
+ // Show usage instructions
153
+ header('How to Use');
154
+ info('Import and use the middleware in your index.ts:');
155
+ newline();
156
+ code('import { Burger } from "burger-api";');
157
+ results.success.forEach((name) => {
158
+ code(
159
+ `import { ${name} } from "./ecosystem/middleware/${name}/${name}";`
160
+ );
161
+ });
162
+ newline();
163
+ code('const app = new Burger({');
164
+ code(' apiDir: "./api",');
165
+ code(' globalMiddleware: [');
166
+ results.success.forEach((name) => {
167
+ code(` ${name}(),`);
168
+ });
169
+ code(' ],');
170
+ code('});');
171
+ newline();
172
+ newline();
173
+
174
+ info('Check each middleware README for configuration options:');
175
+ results.success.forEach((name) => {
176
+ bullet(`ecosystem/middleware/${name}/README.md`);
177
+ });
178
+ newline();
179
+ }
180
+
181
+ if (results.failed.length > 0) {
182
+ warning(`Failed to add ${results.failed.length} middleware:`);
183
+ results.failed.forEach((name) => bullet(name));
184
+ newline();
185
+ info('Run "burger-api list" to see available middleware.');
186
+ newline();
187
+ }
188
+
189
+ if (results.skipped.length > 0) {
190
+ info(`Skipped ${results.skipped.length} middleware:`);
191
+ results.skipped.forEach((name) => bullet(name));
192
+ newline();
193
+ }
194
+
195
+ if (results.success.length > 0) {
196
+ clack.outro('Middleware added successfully!');
197
+ } else {
198
+ clack.outro('No middleware were added');
199
+ process.exit(1);
200
+ }
201
+ });
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Build Commands
3
+ *
4
+ * Two commands for packaging your Burger API project:
5
+ * 1. `burger-api build <file>` - Bundle to single JS file
6
+ * 2. `burger-api build:executable <file>` - Compile to standalone executable
7
+ *
8
+ * These are wrappers around Bun's build commands with sensible defaults.
9
+ */
10
+
11
+ import { Command } from 'commander';
12
+ import { existsSync, readFileSync } from 'fs';
13
+ import { join } from 'path';
14
+ import {
15
+ spinner,
16
+ success,
17
+ error as logError,
18
+ info,
19
+ newline,
20
+ formatSize,
21
+ dim,
22
+ } from '../utils/logger';
23
+
24
+ /**
25
+ * Build command options
26
+ */
27
+ interface BuildCommandOptions {
28
+ outfile: string;
29
+ minify?: boolean;
30
+ sourcemap?: string;
31
+ target?: string;
32
+ }
33
+
34
+ /**
35
+ * Build executable command options
36
+ */
37
+ interface BuildExecutableOptions {
38
+ outfile?: string;
39
+ target?: string;
40
+ minify: boolean;
41
+ bytecode: boolean;
42
+ }
43
+
44
+ /**
45
+ * Create the "build" command
46
+ * Bundles your code into a single JavaScript file
47
+ */
48
+ export const buildCommand = new Command('build')
49
+ .description('Bundle your project to a single JavaScript file')
50
+ .argument('<file>', 'Entry file to build (e.g., index.ts)')
51
+ .option('--outfile <path>', 'Output file path', '.build/bundle.js')
52
+ .option('--minify', 'Minify the output')
53
+ .option(
54
+ '--sourcemap <type>',
55
+ 'Generate sourcemaps (inline, linked, or none)'
56
+ )
57
+ .option('--target <target>', 'Target environment (e.g., bun, node)')
58
+ .action(async (file: string, options: BuildCommandOptions) => {
59
+ // Check if the input file exists
60
+ if (!existsSync(file)) {
61
+ logError(`File not found: ${file}`);
62
+ process.exit(1);
63
+ }
64
+
65
+ const spin = spinner('Building project...');
66
+
67
+ try {
68
+ // Build the command arguments for Bun
69
+ const args = ['build', file];
70
+
71
+ // Add output file
72
+ args.push('--outfile', options.outfile);
73
+
74
+ // Add optional flags
75
+ if (options.minify) {
76
+ args.push('--minify');
77
+ }
78
+
79
+ if (options.sourcemap) {
80
+ args.push('--sourcemap', options.sourcemap);
81
+ }
82
+
83
+ // Always target bun by default since BurgerAPI uses Bun builtins
84
+ args.push('--target', options.target || 'bun');
85
+
86
+ // Run the build using Bun.spawn
87
+ const proc = Bun.spawn(['bun', ...args], {
88
+ stdout: 'pipe',
89
+ stderr: 'pipe',
90
+ });
91
+
92
+ // Wait for it to complete
93
+ const exitCode = await proc.exited;
94
+
95
+ if (exitCode !== 0) {
96
+ // Read error output
97
+ const errorText = await new Response(proc.stderr).text();
98
+ spin.stop('Build failed', true);
99
+ logError(errorText || 'Build process failed');
100
+ process.exit(1);
101
+ }
102
+
103
+ // Get file size
104
+ const outputFile = Bun.file(options.outfile);
105
+ const size = outputFile.size;
106
+
107
+ spin.stop('Build completed successfully!');
108
+ newline();
109
+ success(`Output: ${options.outfile}`);
110
+ info(`Size: ${formatSize(size)}`);
111
+ newline();
112
+ dim('Run your bundle with: bun ' + options.outfile);
113
+ newline();
114
+ } catch (err) {
115
+ spin.stop('Build failed', true);
116
+ logError(err instanceof Error ? err.message : 'Unknown error');
117
+ process.exit(1);
118
+ }
119
+ });
120
+
121
+ /**
122
+ * Create the "build:executable" command
123
+ * Compiles your code to a standalone executable
124
+ */
125
+ export const buildExecutableCommand = new Command('build:executable')
126
+ .description('Compile your project to a standalone executable')
127
+ .argument('<file>', 'Entry file to compile (e.g., index.ts)')
128
+ .option('--outfile <path>', 'Output file path')
129
+ .option(
130
+ '--target <target>',
131
+ 'Target platform (bun-windows-x64, bun-linux-x64, bun-darwin-arm64)'
132
+ )
133
+ .option('--minify', 'Minify the output (enabled by default)', true)
134
+ .option('--no-bytecode', 'Disable bytecode compilation')
135
+ .action(async (file: string, options: BuildExecutableOptions) => {
136
+ // Check if the input file exists
137
+ if (!existsSync(file)) {
138
+ logError(`File not found: ${file}`);
139
+ process.exit(1);
140
+ }
141
+
142
+ // Determine output filename
143
+ let outfile = options.outfile;
144
+ if (!outfile) {
145
+ // Get project name from package.json or use basename
146
+ const projectName = getProjectName();
147
+
148
+ // Add platform-specific extension
149
+ // Check if targeting Windows or if we're on Windows without a specific target
150
+ const isWindows =
151
+ options.target?.includes('windows') ||
152
+ (!options.target && process.platform === 'win32');
153
+
154
+ if (isWindows) {
155
+ outfile = `.build/${projectName}.exe`;
156
+ } else {
157
+ outfile = `.build/${projectName}`;
158
+ }
159
+ }
160
+
161
+ const spin = spinner('Compiling to executable...');
162
+
163
+ try {
164
+ // Build the command arguments for Bun
165
+ const args = ['build', file, '--compile'];
166
+
167
+ // Add output file
168
+ args.push('--outfile', outfile);
169
+
170
+ // Add target platform
171
+ if (options.target) {
172
+ args.push('--target', options.target);
173
+ }
174
+
175
+ // Add minify (on by default)
176
+ if (options.minify) {
177
+ args.push('--minify');
178
+ }
179
+
180
+ // Add bytecode (on by default, unless --no-bytecode is passed)
181
+ if (options.bytecode !== false) {
182
+ args.push('--bytecode');
183
+ }
184
+
185
+ spin.update('Compiling... (this may take a minute)');
186
+
187
+ // Run the build using Bun.spawn
188
+ const proc = Bun.spawn(['bun', ...args], {
189
+ stdout: 'pipe',
190
+ stderr: 'pipe',
191
+ });
192
+
193
+ // Wait for it to complete
194
+ const exitCode = await proc.exited;
195
+
196
+ if (exitCode !== 0) {
197
+ // Read error output
198
+ const errorText = await new Response(proc.stderr).text();
199
+ spin.stop('Compilation failed', true);
200
+ logError(errorText || 'Compilation process failed');
201
+ process.exit(1);
202
+ }
203
+
204
+ // Get file size - check if file exists first
205
+ let size = 0;
206
+ if (existsSync(outfile)) {
207
+ const executableFile = Bun.file(outfile);
208
+ size = executableFile.size;
209
+ }
210
+
211
+ spin.stop('Compilation completed successfully!');
212
+ newline();
213
+ success(`Executable: ${outfile}`);
214
+ if (size > 0) {
215
+ info(`Size: ${formatSize(size)}`);
216
+ }
217
+ newline();
218
+ info('Your standalone executable is ready to run!');
219
+
220
+ if (process.platform !== 'win32') {
221
+ dim(`Make it executable: chmod +x ${outfile}`);
222
+ dim(`Run it: ./${outfile}`);
223
+ } else {
224
+ dim(`Run it: ${outfile}`);
225
+ }
226
+ newline();
227
+ } catch (err) {
228
+ spin.stop('Compilation failed', true);
229
+ logError(err instanceof Error ? err.message : 'Unknown error');
230
+ process.exit(1);
231
+ }
232
+ });
233
+
234
+ /**
235
+ * Get the project name from package.json
236
+ * Falls back to 'app' if not found
237
+ */
238
+ function getProjectName(): string {
239
+ try {
240
+ const packageJsonPath = join(process.cwd(), 'package.json');
241
+ if (existsSync(packageJsonPath)) {
242
+ const content = readFileSync(packageJsonPath, 'utf-8');
243
+ const packageJson = JSON.parse(content);
244
+ return packageJson?.name || 'app';
245
+ }
246
+ } catch (err) {
247
+ // Ignore errors
248
+ }
249
+ return 'app';
250
+ }