@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 +1 -1
- package/package.json +1 -1
- package/src/utils/build/bun.ts +134 -0
- package/src/utils/build/entry.ts +74 -0
- package/src/utils/build/pipeline.ts +90 -0
- package/src/utils/build/project.ts +21 -0
- package/src/utils/templates.ts +2 -2
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.
|
|
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
|
@@ -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
|
+
}
|
package/src/utils/templates.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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>
|