import { AstroBuild } from "./astro-build.ts"; import type { BuildSettings } from "./build-settings.ts"; import type { BuildStrategy } from "./build-strategy.ts"; import { BunBuild } from "./bun-build.ts"; import { FRAMEWORKS, type FrameworkBuildType } from "./config/frameworks.ts"; import { NestjsBuild } from "./nestjs-build.ts"; import { NextjsBuild } from "./nextjs-build.ts"; import { NuxtBuild } from "./nuxt-build.ts"; import { TanstackStartBuild } from "./tanstack-start-build.ts"; import type { BuildCommandIo } from "./workspace.ts"; /** A concrete framework build type, or `"auto"` to detect it. */ export type BuildType = FrameworkBuildType | "auto"; /** * Consumer hooks shared across `createBuildStrategy` / `resolveBuildStrategy`. * All optional: the CLI passes none, while a headless consumer injects build * `env`, taps output, and requires standalone Next output. */ export interface BuildStrategyHooks { /** Build `env` and per-line output tap, forwarded to the build command. */ io?: BuildCommandIo; /** Fail instead of falling back to the full-tree Next `next start` artifact. */ requireStandalone?: boolean; } /** * Detection order, derived from the framework registry so a new framework is * picked up automatically. `bun` is forced last — it is the universal fallback * (its `canBuild` always returns true). */ const DETECTION_ORDER: FrameworkBuildType[] = (() => { const buildTypes = [ ...new Set(FRAMEWORKS.map((framework) => framework.buildType)), ]; return [...buildTypes.filter((type) => type !== "bun"), "bun"]; })(); /** * Constructs the build strategy for an explicit framework build type, wiring * through optional committed build settings and (for bun) the entrypoint. */ export function createBuildStrategy( options: { appPath: string; buildType: FrameworkBuildType; entrypoint?: string; buildSettings?: BuildSettings; } & BuildStrategyHooks, ): BuildStrategy { const { appPath, buildSettings, io } = options; switch (options.buildType) { case "nextjs": return new NextjsBuild({ appPath, buildSettings, io, requireStandalone: options.requireStandalone, }); case "nuxt": return new NuxtBuild({ appPath, io }); case "astro": return new AstroBuild({ appPath, io }); case "nestjs": return new NestjsBuild({ appPath, buildSettings, io }); case "tanstack-start": return new TanstackStartBuild({ appPath, buildSettings, io }); case "bun": return new BunBuild({ appPath, entrypoint: options.entrypoint, buildSettings, io, }); } } /** * Resolves the build strategy and the concrete build type for an app. With an * explicit `buildType` the matching strategy is returned directly; with * `"auto"` (or omitted) each framework is tried in detection order and the * first match wins, with bun as the fallback. Returning the resolved build * type lets a consumer report what it detected. */ export async function resolveBuildStrategy( options: { appPath: string; buildType?: BuildType; entrypoint?: string; buildSettings?: BuildSettings; signal?: AbortSignal; } & BuildStrategyHooks, ): Promise<{ strategy: BuildStrategy; buildType: FrameworkBuildType }> { const shared = { appPath: options.appPath, entrypoint: options.entrypoint, buildSettings: options.buildSettings, io: options.io, requireStandalone: options.requireStandalone, }; if (options.buildType && options.buildType !== "auto") { return { buildType: options.buildType, strategy: createBuildStrategy({ ...shared, buildType: options.buildType, }), }; } for (const buildType of DETECTION_ORDER) { if (buildType === "bun") { break; } const strategy = createBuildStrategy({ ...shared, buildType }); options.signal?.throwIfAborted(); if (await strategy.canBuild(options.signal)) { return { buildType, strategy }; } } return { buildType: "bun", strategy: createBuildStrategy({ ...shared, buildType: "bun" }), }; }