import { AlertBlock } from "@/components/shared/alert-block"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Cog } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; export enum BuildType { dockerfile = "dockerfile", heroku_buildpacks = "heroku_buildpacks", paketo_buildpacks = "paketo_buildpacks", nixpacks = "nixpacks", static = "static", railpack = "railpack", } const buildTypeDisplayMap: Record = { [BuildType.dockerfile]: "Dockerfile", [BuildType.railpack]: "Railpack", [BuildType.nixpacks]: "Nixpacks", [BuildType.heroku_buildpacks]: "Heroku Buildpacks", [BuildType.paketo_buildpacks]: "Paketo Buildpacks", [BuildType.static]: "Static", }; const mySchema = z.discriminatedUnion("buildType", [ z.object({ buildType: z.literal(BuildType.dockerfile), dockerfile: z .string({ required_error: "Dockerfile path is required", invalid_type_error: "Dockerfile path is required", }) .min(1, "Dockerfile required"), dockerContextPath: z.string().nullable().default(""), dockerBuildStage: z.string().nullable().default(""), }), z.object({ buildType: z.literal(BuildType.heroku_buildpacks), herokuVersion: z.string().nullable().default(""), }), z.object({ buildType: z.literal(BuildType.paketo_buildpacks), }), z.object({ buildType: z.literal(BuildType.nixpacks), publishDirectory: z.string().optional(), }), z.object({ buildType: z.literal(BuildType.static), }), z.object({ buildType: z.literal(BuildType.railpack), }), ]); type AddTemplate = z.infer; interface Props { applicationId: string; } interface ApplicationData { buildType: BuildType; dockerfile?: string | null; dockerContextPath?: string | null; dockerBuildStage?: string | null; herokuVersion?: string | null; publishDirectory?: string | null; } function isValidBuildType(value: string): value is BuildType { return Object.values(BuildType).includes(value as BuildType); } const resetData = (data: ApplicationData): AddTemplate => { switch (data.buildType) { case BuildType.dockerfile: return { buildType: BuildType.dockerfile, dockerfile: data.dockerfile || "", dockerContextPath: data.dockerContextPath || "", dockerBuildStage: data.dockerBuildStage || "", }; case BuildType.heroku_buildpacks: return { buildType: BuildType.heroku_buildpacks, herokuVersion: data.herokuVersion || "", }; case BuildType.nixpacks: return { buildType: BuildType.nixpacks, publishDirectory: data.publishDirectory || undefined, }; case BuildType.paketo_buildpacks: return { buildType: BuildType.paketo_buildpacks, }; case BuildType.static: return { buildType: BuildType.static, }; case BuildType.railpack: return { buildType: BuildType.railpack, }; default: const buildType = data.buildType as BuildType; return { buildType, } as AddTemplate; } }; export const ShowBuildChooseForm = ({ applicationId }: Props) => { const { mutateAsync, isLoading } = api.application.saveBuildType.useMutation(); const { data, refetch } = api.application.one.useQuery( { applicationId }, { enabled: !!applicationId }, ); const form = useForm({ defaultValues: { buildType: BuildType.nixpacks, }, resolver: zodResolver(mySchema), }); const buildType = form.watch("buildType"); useEffect(() => { if (data) { const typedData: ApplicationData = { ...data, buildType: isValidBuildType(data.buildType) ? (data.buildType as BuildType) : BuildType.nixpacks, // fallback }; form.reset(resetData(typedData)); } }, [data, form]); const onSubmit = async (data: AddTemplate) => { await mutateAsync({ applicationId, buildType: data.buildType, publishDirectory: data.buildType === BuildType.nixpacks ? data.publishDirectory : null, dockerfile: data.buildType === BuildType.dockerfile ? data.dockerfile : null, dockerContextPath: data.buildType === BuildType.dockerfile ? data.dockerContextPath : null, dockerBuildStage: data.buildType === BuildType.dockerfile ? data.dockerBuildStage : null, herokuVersion: data.buildType === BuildType.heroku_buildpacks ? data.herokuVersion : null, }) .then(async () => { toast.success("Build type saved"); await refetch(); }) .catch(() => { toast.error("Error saving the build type"); }); }; return (
Build Type

Select the way of building your code

Builders can consume significant memory and CPU resources (recommended: 4+ GB RAM and 2+ CPU cores). For production environments, please review our{" "} Production Guide {" "} for best practices and optimization recommendations. Builders are suitable for development and prototyping purposes when you have sufficient resources available. ( Build Type {Object.entries(buildTypeDisplayMap).map( ([value, label]) => ( {label} {value === BuildType.railpack && ( New )} ), )} )} /> {buildType === BuildType.heroku_buildpacks && ( ( Heroku Version (Optional) )} /> )} {buildType === BuildType.dockerfile && ( <> ( Docker File )} /> ( Docker Context Path )} /> (
Docker Build Stage Allows you to target a specific stage in a Multi-stage Dockerfile. If empty, Docker defaults to build the last defined stage.
)} /> )} {buildType === BuildType.nixpacks && ( (
Publish Directory Allows you to serve a single directory via NGINX after the build phase. Useful if the final build assets should be served as a static site.
)} /> )}
); };