@burger-api/cli 0.9.1 → 0.9.3

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/CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  All notable changes to the Burger API CLI will be documented in this file.
4
4
 
5
- ## Version 0.9.0 - (March 15, 2026)
5
+ ## Version 0.9.3 - (March 17, 2026)
6
6
 
7
7
  - ✨ **Create** – New projects get a config file (`burger.config.ts`) from
8
8
  your answers; the build uses this config when present.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burger-api/cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Simple command-line tool for Burger API projects",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -0,0 +1,134 @@
1
+ import { resolve } from 'path';
2
+
3
+ function formatBunBuildLogs(logs: unknown): string {
4
+ if (!Array.isArray(logs) || logs.length === 0) {
5
+ return '';
6
+ }
7
+
8
+ const messages: string[] = [];
9
+ for (const item of logs) {
10
+ if (!item || typeof item !== 'object') continue;
11
+
12
+ const log = item as {
13
+ level?: string;
14
+ message?: string;
15
+ text?: string;
16
+ name?: string;
17
+ position?: { file?: string; line?: number; column?: number };
18
+ };
19
+ const text =
20
+ (typeof log.message === 'string' && log.message) ||
21
+ (typeof log.text === 'string' && log.text) ||
22
+ (typeof log.name === 'string' && log.name) ||
23
+ '';
24
+ if (!text) continue;
25
+
26
+ const level =
27
+ typeof log.level === 'string' ? log.level.toUpperCase() : 'ERROR';
28
+ const file = log.position?.file ? `${log.position.file}` : '';
29
+ const line =
30
+ typeof log.position?.line === 'number'
31
+ ? `:${log.position.line}`
32
+ : '';
33
+ const column =
34
+ typeof log.position?.column === 'number'
35
+ ? `:${log.position.column}`
36
+ : '';
37
+ const location = file ? ` (${file}${line}${column})` : '';
38
+ messages.push(`- [${level}] ${text}${location}`);
39
+ }
40
+ return messages.join('\n');
41
+ }
42
+
43
+ function extractBunBuildDetails(err: unknown): string {
44
+ if (!err || typeof err !== 'object') {
45
+ return '';
46
+ }
47
+
48
+ const anyErr = err as {
49
+ logs?: unknown;
50
+ errors?: unknown;
51
+ cause?: { logs?: unknown; errors?: unknown };
52
+ };
53
+ const candidates = [
54
+ anyErr.logs,
55
+ anyErr.errors,
56
+ anyErr.cause?.logs,
57
+ anyErr.cause?.errors,
58
+ ];
59
+
60
+ for (const candidate of candidates) {
61
+ const detail = formatBunBuildLogs(candidate);
62
+ if (detail) return detail;
63
+ }
64
+ return '';
65
+ }
66
+
67
+ export function createBunBuildOptions(options: {
68
+ entryPath: string;
69
+ outDir: string;
70
+ cwd: string;
71
+ outfile: string;
72
+ target?: string;
73
+ minify?: boolean;
74
+ sourcemap?: string;
75
+ compile?: boolean;
76
+ bytecode?: boolean;
77
+ }): Parameters<typeof Bun.build>[0] {
78
+ const buildOptions: Parameters<typeof Bun.build>[0] = {
79
+ entrypoints: [options.entryPath],
80
+ outdir: options.outDir,
81
+ target: (options.target as 'bun') || 'bun',
82
+ minify: options.minify ?? false,
83
+ splitting: false,
84
+ sourcemap:
85
+ options.sourcemap === undefined
86
+ ? undefined
87
+ : (options.sourcemap as 'none' | 'linked' | 'inline' | 'external'),
88
+ };
89
+
90
+ const ext = buildOptions as unknown as Record<string, unknown>;
91
+ ext.naming = {
92
+ chunk: '[name]-[hash].[ext]',
93
+ asset: '[name]-[hash].[ext]',
94
+ };
95
+
96
+ if (options.compile) {
97
+ ext.compile = {
98
+ outfile: resolve(options.cwd, options.outfile),
99
+ ...(options.target && { target: options.target }),
100
+ };
101
+ if (options.bytecode !== false) {
102
+ ext.bytecode = true;
103
+ }
104
+ delete ext.outdir;
105
+ }
106
+
107
+ return buildOptions;
108
+ }
109
+
110
+ export async function runBunBuildOrThrow(
111
+ buildOptions: Parameters<typeof Bun.build>[0]
112
+ ): Promise<Awaited<ReturnType<typeof Bun.build>>> {
113
+ let result: Awaited<ReturnType<typeof Bun.build>>;
114
+ try {
115
+ result = await Bun.build(buildOptions);
116
+ } catch (err) {
117
+ const detail = extractBunBuildDetails(err);
118
+ const message = err instanceof Error ? err.message : 'Bun.build failed.';
119
+ if (detail) {
120
+ throw new Error(`${message}\n${detail}`);
121
+ }
122
+ throw new Error(message);
123
+ }
124
+
125
+ if (!result.success) {
126
+ const detail = formatBunBuildLogs((result as { logs?: unknown }).logs);
127
+ if (detail) {
128
+ throw new Error(`Bun.build failed.\n${detail}`);
129
+ }
130
+ throw new Error('Bun.build failed.');
131
+ }
132
+
133
+ return result;
134
+ }
@@ -0,0 +1,74 @@
1
+ import { dirname, resolve } from 'path';
2
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'fs';
3
+
4
+ export function prepareVirtualEntry(options: {
5
+ cwd: string;
6
+ outfile: string;
7
+ pageDir: string;
8
+ source: string;
9
+ hasPages: boolean;
10
+ }): { outDir: string; virtualSourcePath: string; virtualPath: string } {
11
+ const outDir = resolve(options.cwd, dirname(options.outfile));
12
+ mkdirSync(outDir, { recursive: true });
13
+
14
+ const virtualEntryDir = options.hasPages
15
+ ? resolve(options.cwd, options.pageDir)
16
+ : outDir;
17
+ mkdirSync(virtualEntryDir, { recursive: true });
18
+
19
+ const virtualSourcePath = resolve(virtualEntryDir, '__burger_build_entry__.ts');
20
+ const virtualPath = virtualSourcePath.split('\\').join('/');
21
+ writeFileSync(virtualSourcePath, options.source, 'utf-8');
22
+
23
+ return { outDir, virtualSourcePath, virtualPath };
24
+ }
25
+
26
+ export function cleanupVirtualEntry(virtualSourcePath: string): void {
27
+ if (existsSync(virtualSourcePath)) {
28
+ unlinkSync(virtualSourcePath);
29
+ }
30
+ }
31
+
32
+ export async function finalizeBuildOutputs(options: {
33
+ result: Awaited<ReturnType<typeof Bun.build>>;
34
+ cwd: string;
35
+ outfile: string;
36
+ outDir: string;
37
+ compile?: boolean;
38
+ }): Promise<{ path: string; size: number }[]> {
39
+ const desiredOut = resolve(options.cwd, options.outfile);
40
+ const outputs: { path: string; size: number }[] = [];
41
+
42
+ if (!options.result.outputs?.length) {
43
+ return outputs;
44
+ }
45
+
46
+ const first = options.result.outputs[0] as Blob & { path?: string };
47
+ const outPath = first.path ? resolve(first.path) : undefined;
48
+
49
+ const entryArtifacts = new Set<string>([
50
+ resolve(options.outDir, '__burger_build_entry__.js'),
51
+ ]);
52
+ if (outPath?.endsWith('__burger_build_entry__.js')) {
53
+ entryArtifacts.add(outPath);
54
+ }
55
+
56
+ if (!options.compile && outPath && outPath !== desiredOut) {
57
+ const blob = first as Blob;
58
+ await Bun.write(desiredOut, blob);
59
+ outputs.push({ path: desiredOut, size: blob.size });
60
+ } else {
61
+ outputs.push({
62
+ path: outPath ?? desiredOut,
63
+ size: (first as Blob).size ?? 0,
64
+ });
65
+ }
66
+
67
+ for (const entryArtifact of entryArtifacts) {
68
+ if (entryArtifact !== desiredOut && existsSync(entryArtifact)) {
69
+ unlinkSync(entryArtifact);
70
+ }
71
+ }
72
+
73
+ return outputs;
74
+ }
@@ -0,0 +1,90 @@
1
+ import { resolveBuildConfig } from '../config';
2
+ import { scanApiRoutes, scanPageRoutes } from '../scanner';
3
+ import { generateVirtualEntrySource } from '../virtual-entry';
4
+ import { createBunBuildOptions, runBunBuildOrThrow } from './bun';
5
+ import {
6
+ cleanupVirtualEntry,
7
+ finalizeBuildOutputs,
8
+ prepareVirtualEntry,
9
+ } from './entry';
10
+ import {
11
+ cleanupEntryOptionsModule,
12
+ prepareEntryOptionsModule,
13
+ } from '../entry-options';
14
+
15
+ export interface VirtualBuildResult {
16
+ success: boolean;
17
+ hasPages: boolean;
18
+ outputs: { path: string; size: number }[];
19
+ }
20
+
21
+ export async function runVirtualEntryBuild(options: {
22
+ cwd: string;
23
+ entryFile: string;
24
+ outfile: string;
25
+ target?: string;
26
+ minify?: boolean;
27
+ sourcemap?: string;
28
+ compile?: boolean;
29
+ bytecode?: boolean;
30
+ }): Promise<VirtualBuildResult> {
31
+ const config = await resolveBuildConfig(options.cwd);
32
+ const entryOptions = prepareEntryOptionsModule({
33
+ cwd: options.cwd,
34
+ entryFile: options.entryFile,
35
+ });
36
+
37
+ const [apiEntries, pageEntries] = await Promise.all([
38
+ scanApiRoutes(options.cwd, config.apiDir, config.apiPrefix),
39
+ scanPageRoutes(options.cwd, config.pageDir, config.pagePrefix),
40
+ ]);
41
+
42
+ if (apiEntries.length === 0 && pageEntries.length === 0) {
43
+ throw new Error(
44
+ `No routes found. Ensure ${config.apiDir} or ${config.pageDir} ` +
45
+ `exist and contain route.ts files or page files.`
46
+ );
47
+ }
48
+
49
+ const source = generateVirtualEntrySource(
50
+ config,
51
+ apiEntries,
52
+ pageEntries,
53
+ entryOptions.importPath
54
+ );
55
+ const hasPages = pageEntries.length > 0;
56
+ const { outDir, virtualPath, virtualSourcePath } = prepareVirtualEntry({
57
+ cwd: options.cwd,
58
+ outfile: options.outfile,
59
+ pageDir: config.pageDir,
60
+ source,
61
+ hasPages,
62
+ });
63
+
64
+ const buildOptions = createBunBuildOptions({
65
+ entryPath: virtualPath,
66
+ outDir,
67
+ cwd: options.cwd,
68
+ outfile: options.outfile,
69
+ target: options.target,
70
+ minify: options.minify,
71
+ sourcemap: options.sourcemap,
72
+ compile: options.compile,
73
+ bytecode: options.bytecode,
74
+ });
75
+
76
+ try {
77
+ const result = await runBunBuildOrThrow(buildOptions);
78
+ const outputs = await finalizeBuildOutputs({
79
+ result,
80
+ cwd: options.cwd,
81
+ outfile: options.outfile,
82
+ outDir,
83
+ compile: options.compile,
84
+ });
85
+ return { success: result.success ?? false, hasPages, outputs };
86
+ } finally {
87
+ cleanupVirtualEntry(virtualSourcePath);
88
+ cleanupEntryOptionsModule(entryOptions.tempFilePath);
89
+ }
90
+ }
@@ -0,0 +1,21 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ /**
5
+ * Read the project name from package.json.
6
+ * Falls back to 'app' if not found or on any error.
7
+ */
8
+ export function getProjectName(cwd: string = process.cwd()): string {
9
+ try {
10
+ const packageJsonPath = join(cwd, 'package.json');
11
+ if (existsSync(packageJsonPath)) {
12
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
13
+ name?: string;
14
+ };
15
+ return pkg?.name || 'app';
16
+ }
17
+ } catch {
18
+ // Ignore — use fallback
19
+ }
20
+ return 'app';
21
+ }
@@ -30,7 +30,7 @@ export function generatePackageJson(projectName: string): string {
30
30
  build: 'bun build src/index.ts --outdir ./dist',
31
31
  },
32
32
  dependencies: {
33
- 'burger-api': '^0.9.0',
33
+ 'burger-api': '^0.9.3',
34
34
  },
35
35
  devDependencies: {
36
36
  '@types/bun': 'latest',
@@ -962,7 +962,7 @@ export function generateIndexPage(projectName: string): string {
962
962
 
963
963
  <!-- Footer -->
964
964
  <footer class="footer">
965
- <div class="version">BurgerAPI v0.9.0 • Bun v1.3+</div>
965
+ <div class="version">BurgerAPI v0.9.3 • Bun v1.3+</div>
966
966
  <div class="social-links">
967
967
  <a href="https://github.com/isfhan/burger-api" target="_blank">GitHub</a>
968
968
  <a href="https://www.npmjs.com/package/burger-api" target="_blank">NPM</a>