@burger-api/cli 0.7.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.
@@ -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
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Build config resolution: conventions-first with optional burger.config.ts
3
+ *
4
+ * Used by the CLI build pipeline to discover apiDir, pageDir, and prefixes
5
+ * without parsing the user's entry file.
6
+ */
7
+
8
+ import { existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import type { BuildConfig } from '../types/index';
11
+
12
+ const CONVENTION_DEFAULTS: BuildConfig = {
13
+ apiDir: './src/api',
14
+ pageDir: './src/pages',
15
+ apiPrefix: '/api',
16
+ pagePrefix: '/',
17
+ debug: false,
18
+ };
19
+
20
+ const CONFIG_NAMES = ['burger.config.ts', 'burger.config.js'];
21
+
22
+ /**
23
+ * Resolve build configuration from the project directory.
24
+ * Uses convention defaults; overrides with burger.config.ts / burger.config.js if present.
25
+ *
26
+ * @param cwd - Project root (e.g. process.cwd())
27
+ * @returns BuildConfig with resolved paths and prefixes
28
+ */
29
+ export async function resolveBuildConfig(cwd: string): Promise<BuildConfig> {
30
+ let configPath: string | null = null;
31
+ for (const name of CONFIG_NAMES) {
32
+ const candidate = join(cwd, name);
33
+ if (existsSync(candidate)) {
34
+ configPath = candidate;
35
+ break;
36
+ }
37
+ }
38
+
39
+ if (!configPath) {
40
+ return { ...CONVENTION_DEFAULTS };
41
+ }
42
+
43
+ try {
44
+ const mod = await import(configPath);
45
+ const user = mod.default ?? mod;
46
+ if (!user || typeof user !== 'object') {
47
+ return { ...CONVENTION_DEFAULTS };
48
+ }
49
+ return mergeBuildConfig(CONVENTION_DEFAULTS, user);
50
+ } catch (err) {
51
+ console.warn(
52
+ `[burger-api] Could not load ${configPath}: ${err instanceof Error ? err.message : String(err)}. Using convention defaults.`
53
+ );
54
+ return { ...CONVENTION_DEFAULTS };
55
+ }
56
+ }
57
+
58
+ function mergeBuildConfig(
59
+ defaults: BuildConfig,
60
+ user: Record<string, unknown>
61
+ ): BuildConfig {
62
+ return {
63
+ apiDir:
64
+ typeof user.apiDir === 'string' ? user.apiDir : defaults.apiDir,
65
+ pageDir:
66
+ typeof user.pageDir === 'string' ? user.pageDir : defaults.pageDir,
67
+ apiPrefix:
68
+ typeof user.apiPrefix === 'string'
69
+ ? user.apiPrefix
70
+ : defaults.apiPrefix,
71
+ pagePrefix:
72
+ typeof user.pagePrefix === 'string'
73
+ ? user.pagePrefix
74
+ : defaults.pagePrefix,
75
+ debug:
76
+ typeof user.debug === 'boolean' ? user.debug : defaults.debug,
77
+ };
78
+ }