From 10eca057ec5e74bcbd150027c96f4bd5aaa1bbff Mon Sep 17 00:00:00 2001 From: thuggys <150315417+thuggys@users.noreply.github.com> Date: Sat, 31 Jan 2026 16:35:36 -0500 Subject: [PATCH 1/4] feat: add docker-compose addon support with templates and validation - Updated config validation to include backend and runtime parameters for addons. - Enhanced addon tests for docker-compose compatibility with various frontends and backends. - Implemented docker-compose file generation with appropriate Dockerfiles and .dockerignore files for server and web applications. - Added support for PostgreSQL, MySQL, and MongoDB databases in docker-compose configurations. - Introduced new templates for Dockerfiles and nginx configuration tailored for different setups. - Updated types to include docker-compose in the addons schema. --- apps/cli/src/constants.ts | 1 + apps/cli/src/prompts/addons.ts | 35 +- apps/cli/src/prompts/config-prompts.ts | 9 +- apps/cli/src/utils/compatibility-rules.ts | 35 +- apps/cli/src/utils/config-validation.ts | 16 +- apps/cli/test/addons.test.ts | 286 +++++++++++++- bun.lock | 4 +- .../src/template-handlers/addons.ts | 48 +++ .../src/templates.generated.ts | 374 +++++++++++++++++- .../addons/docker-compose/.dockerignore.hbs | 7 + .../apps/server/.dockerignore.hbs | 12 + .../docker-compose/apps/server/Dockerfile.hbs | 43 ++ .../docker-compose/apps/web/.dockerignore.hbs | 13 + .../docker-compose/apps/web/Dockerfile.hbs | 40 ++ .../apps/web/Dockerfile.next.hbs | 83 ++++ .../apps/web/Dockerfile.vite.hbs | 29 ++ .../docker-compose/apps/web/nginx.conf.hbs | 24 ++ .../docker-compose/docker-compose.yml.hbs | 112 ++++++ packages/types/src/schemas.ts | 1 + 19 files changed, 1158 insertions(+), 14 deletions(-) create mode 100644 packages/template-generator/templates/addons/docker-compose/.dockerignore.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/apps/server/.dockerignore.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/apps/server/Dockerfile.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/apps/web/.dockerignore.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.next.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.vite.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/apps/web/nginx.conf.hbs create mode 100644 packages/template-generator/templates/addons/docker-compose/docker-compose.yml.hbs diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index e5e6ec9a4..68b2d27dd 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -60,6 +60,7 @@ export const ADDON_COMPATIBILITY = { fumadocs: [], opentui: [], wxt: [], + "docker-compose": [], skills: [], none: [], } as const; diff --git a/apps/cli/src/prompts/addons.ts b/apps/cli/src/prompts/addons.ts index 21cd99192..e81d45db3 100644 --- a/apps/cli/src/prompts/addons.ts +++ b/apps/cli/src/prompts/addons.ts @@ -1,5 +1,12 @@ import { DEFAULT_CONFIG } from "../constants"; -import { type Addons, AddonsSchema, type Auth, type Frontend } from "../types"; +import { + type Addons, + AddonsSchema, + type Auth, + type Backend, + type Frontend, + type Runtime, +} from "../types"; import { getCompatibleAddons, validateAddonCompatibility } from "../utils/compatibility-rules"; import { UserCancelledError } from "../utils/errors"; import { isCancel, navigableGroupMultiselect } from "./navigable"; @@ -71,6 +78,10 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } { label = "Skills"; hint = "AI coding agent skills for your stack"; break; + case "docker-compose": + label = "Docker Compose"; + hint = "Containerize your app for deployment"; + break; default: label = addon; hint = `Add ${addon}`; @@ -82,11 +93,17 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } { const ADDON_GROUPS = { Tooling: ["turborepo", "biome", "oxlint", "ultracite", "husky", "lefthook"], Documentation: ["starlight", "fumadocs"], - Extensions: ["pwa", "tauri", "opentui", "wxt"], + Extensions: ["pwa", "tauri", "opentui", "wxt", "docker-compose"], AI: ["ruler", "skills"], }; -export async function getAddonsChoice(addons?: Addons[], frontends?: Frontend[], auth?: Auth) { +export async function getAddonsChoice( + addons?: Addons[], + frontends?: Frontend[], + auth?: Auth, + backend?: Backend, + runtime?: Runtime, +) { if (addons !== undefined) return addons; const allAddons = AddonsSchema.options.filter((addon) => addon !== "none"); @@ -100,7 +117,13 @@ export async function getAddonsChoice(addons?: Addons[], frontends?: Frontend[], const frontendsArray = frontends || []; for (const addon of allAddons) { - const { isCompatible } = validateAddonCompatibility(addon, frontendsArray, auth); + const { isCompatible } = validateAddonCompatibility( + addon, + frontendsArray, + auth, + backend, + runtime, + ); if (!isCompatible) continue; const { label, hint } = getAddonDisplay(addon); @@ -152,6 +175,8 @@ export async function getAddonsToAdd( frontend: Frontend[], existingAddons: Addons[] = [], auth?: Auth, + backend?: Backend, + runtime?: Runtime, ) { const groupedOptions: Record = { Tooling: [], @@ -167,6 +192,8 @@ export async function getAddonsToAdd( frontendArray, existingAddons, auth, + backend, + runtime, ); for (const addon of compatibleAddons) { diff --git a/apps/cli/src/prompts/config-prompts.ts b/apps/cli/src/prompts/config-prompts.ts index 5f89cc350..1a0ea9212 100644 --- a/apps/cli/src/prompts/config-prompts.ts +++ b/apps/cli/src/prompts/config-prompts.ts @@ -80,7 +80,14 @@ export async function gatherConfig( auth: ({ results }) => getAuthChoice(flags.auth, results.backend, results.frontend), payments: ({ results }) => getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend), - addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend, results.auth), + addons: ({ results }) => + getAddonsChoice( + flags.addons, + results.frontend, + results.auth, + results.backend, + results.runtime, + ), examples: ({ results }) => getExamplesChoice( flags.examples, diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index fe3d8f32c..bf63e189e 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -9,6 +9,7 @@ import type { Frontend, Payments, ProjectConfig, + Runtime, ServerDeploy, WebDeploy, } from "../types"; @@ -255,6 +256,8 @@ export function validateAddonCompatibility( addon: Addons, frontend: Frontend[], _auth?: Auth, + backend?: Backend, + runtime?: Runtime, ): { isCompatible: boolean; reason?: string } { const compatibleFrontends = ADDON_COMPATIBILITY[addon]; @@ -272,6 +275,22 @@ export function validateAddonCompatibility( } } + // Docker Compose specific compatibility checks + if (addon === "docker-compose") { + if (backend === "convex") { + return { + isCompatible: false, + reason: "docker-compose is not compatible with Convex backend (managed service)", + }; + } + if (runtime === "workers") { + return { + isCompatible: false, + reason: "docker-compose is not compatible with Cloudflare Workers runtime", + }; + } + } + return { isCompatible: true }; } @@ -280,13 +299,15 @@ export function getCompatibleAddons( frontend: Frontend[], existingAddons: Addons[] = [], auth?: Auth, + backend?: Backend, + runtime?: Runtime, ) { return allAddons.filter((addon) => { if (existingAddons.includes(addon)) return false; if (addon === "none") return false; - const { isCompatible } = validateAddonCompatibility(addon, frontend, auth); + const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime); return isCompatible; }); } @@ -295,12 +316,20 @@ export function validateAddonsAgainstFrontends( addons: Addons[] = [], frontends: Frontend[] = [], auth?: Auth, + backend?: Backend, + runtime?: Runtime, ): ValidationResult { for (const addon of addons) { if (addon === "none") continue; - const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth); + const { isCompatible, reason } = validateAddonCompatibility( + addon, + frontends, + auth, + backend, + runtime, + ); if (!isCompatible) { - return validationErr(`Incompatible addon/frontend combination: ${reason}`); + return validationErr(`Incompatible addon combination: ${reason}`); } } return Result.ok(undefined); diff --git a/apps/cli/src/utils/config-validation.ts b/apps/cli/src/utils/config-validation.ts index 154435555..659bf4841 100644 --- a/apps/cli/src/utils/config-validation.ts +++ b/apps/cli/src/utils/config-validation.ts @@ -466,7 +466,13 @@ export function validateFullConfig( } if (config.addons && config.addons.length > 0) { - yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth); + yield* validateAddonsAgainstFrontends( + config.addons, + config.frontend, + config.auth, + config.backend, + config.runtime, + ); config.addons = [...new Set(config.addons)]; } @@ -507,7 +513,13 @@ export function validateConfigForProgrammaticUse(config: Partial) ); if (config.addons && config.addons.length > 0) { - yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth); + yield* validateAddonsAgainstFrontends( + config.addons, + config.frontend, + config.auth, + config.backend, + config.runtime, + ); } yield* validateExamplesCompatibility( diff --git a/apps/cli/test/addons.test.ts b/apps/cli/test/addons.test.ts index d824e75ed..b0c4cd6fe 100644 --- a/apps/cli/test/addons.test.ts +++ b/apps/cli/test/addons.test.ts @@ -1,4 +1,6 @@ -import { describe, it } from "bun:test"; +import { describe, expect, it } from "bun:test"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; import type { Addons, Frontend } from "../src"; @@ -171,6 +173,288 @@ describe("Addon Configurations", () => { }); } }); + + describe("Docker Compose Addon", () => { + it("should work with docker-compose + Hono + postgres + drizzle", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-hono-postgres", + addons: ["docker-compose"], + frontend: ["tanstack-router"], + backend: "hono", + runtime: "bun", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + }); + + it("should work with docker-compose + Next.js + self backend", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-nextjs-self", + addons: ["docker-compose"], + frontend: ["next"], + backend: "self", + runtime: "none", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + }); + + it("should fail with docker-compose + Convex backend", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-convex-fail", + addons: ["docker-compose"], + frontend: ["tanstack-router"], + backend: "convex", + runtime: "none", + database: "none", + orm: "none", + auth: "none", + api: "none", // Convex requires api: "none" + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + expectError: true, + }); + + expectError(result, "docker-compose is not compatible with Convex backend"); + }); + + it("should fail with docker-compose + Workers runtime", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-workers-fail", + addons: ["docker-compose"], + frontend: ["tanstack-router"], + backend: "hono", + runtime: "workers", + database: "sqlite", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "cloudflare", + serverDeploy: "alchemy", // Workers runtime requires server deployment + expectError: true, + }); + + expectError(result, "docker-compose is not compatible with Cloudflare Workers runtime"); + }); + + it("should work with docker-compose + mysql + prisma", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-mysql-prisma", + addons: ["docker-compose"], + frontend: ["react-router"], + backend: "hono", + runtime: "bun", + database: "mysql", + orm: "prisma", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + }); + + it("should work with docker-compose + Nuxt + oRPC", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-nuxt", + addons: ["docker-compose"], + frontend: ["nuxt"], + backend: "hono", + runtime: "bun", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "orpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + }); + + it("should work with docker-compose + Svelte", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-svelte", + addons: ["docker-compose"], + frontend: ["svelte"], + backend: "hono", + runtime: "bun", + database: "sqlite", + orm: "drizzle", + auth: "none", + api: "orpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + }); + + describe("Docker Compose File Generation", () => { + it("should generate docker-compose.yml at project root", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-files-root", + addons: ["docker-compose"], + frontend: ["tanstack-router"], + backend: "hono", + runtime: "bun", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + expect(result.projectDir).toBeDefined(); + + const dockerComposeYml = join(result.projectDir!, "docker-compose.yml"); + expect(existsSync(dockerComposeYml)).toBe(true); + }); + + it("should generate Dockerfile in apps/server when backend exists", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-files-server", + addons: ["docker-compose"], + frontend: ["tanstack-router"], + backend: "hono", + runtime: "bun", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + expect(result.projectDir).toBeDefined(); + + const serverDockerfile = join(result.projectDir!, "apps", "server", "Dockerfile"); + expect(existsSync(serverDockerfile)).toBe(true); + }); + + it("should generate Dockerfile in apps/web for Vite-based frontend", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-files-web", + addons: ["docker-compose"], + frontend: ["tanstack-router"], + backend: "hono", + runtime: "bun", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + expect(result.projectDir).toBeDefined(); + + // Vite-based frontends get Dockerfile.vite + const webDockerfile = join(result.projectDir!, "apps", "web", "Dockerfile.vite"); + expect(existsSync(webDockerfile)).toBe(true); + }); + + it("should generate Dockerfile in apps/web for Next.js + self backend", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-files-nextjs", + addons: ["docker-compose"], + frontend: ["next"], + backend: "self", + runtime: "none", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + expect(result.projectDir).toBeDefined(); + + // With self backend, there should be no server directory but web Dockerfile should exist + const dockerComposeYml = join(result.projectDir!, "docker-compose.yml"); + // Next.js frontend gets Dockerfile.next + const webDockerfile = join(result.projectDir!, "apps", "web", "Dockerfile.next"); + + expect(existsSync(dockerComposeYml)).toBe(true); + expect(existsSync(webDockerfile)).toBe(true); + }); + + it("should generate .dockerignore files", async () => { + const result = await runTRPCTest({ + projectName: "docker-compose-files-ignore", + addons: ["docker-compose"], + frontend: ["tanstack-router"], + backend: "hono", + runtime: "bun", + database: "postgres", + orm: "drizzle", + auth: "none", + api: "trpc", + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + install: false, + }); + + expectSuccess(result); + expect(result.projectDir).toBeDefined(); + + const rootDockerignore = join(result.projectDir!, ".dockerignore"); + expect(existsSync(rootDockerignore)).toBe(true); + }); + }); + }); }); describe("Multiple Addons", () => { diff --git a/bun.lock b/bun.lock index d9bf937d0..c54f672a8 100644 --- a/bun.lock +++ b/bun.lock @@ -318,7 +318,7 @@ "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.7.5", "", { "dependencies": { "@formatjs/fast-memoize": "3.0.3", "tslib": "^2.8.0" } }, "sha512-7/nd90cn5CT7SVF71/ybUKAcnvBlr9nZlJJp8O8xIZHXFgYOC4SXExZlSdgHv2l6utjw1byidL06QzChvQMHwA=="], - "@fumadocs/ui": ["@fumadocs/ui@16.4.10", "", { "dependencies": { "next-themes": "^0.4.6", "postcss-selector-parser": "^7.1.1", "tailwind-merge": "^3.4.0" }, "peerDependencies": { "@types/react": "*", "fumadocs-core": "16.4.10", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.0.0" }, "optionalPeers": ["@types/react", "next", "tailwindcss"] }, "sha512-YD4ESvNr22xpRKVPcGLWP0LwAvvYXHeV42r3UEEbIjhOV0DHpoMZ0B47fWcaZxZ0sXlcG72k8rhrTWkRjsY6Fg=="], + "@fumadocs/ui": ["@fumadocs/ui@16.4.11", "", { "dependencies": { "next-themes": "^0.4.6", "postcss-selector-parser": "^7.1.1", "tailwind-merge": "^3.4.0" }, "peerDependencies": { "@types/react": "*", "fumadocs-core": "16.4.11", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.0.0" }, "optionalPeers": ["@types/react", "next", "tailwindcss"] }, "sha512-3APzHr4Rv5P9YQApTKCQW3cXika0dwHuOo8WxYz74y42nONRo/TMDtvoWaNhB145sBrW9N4j0/0xXfiGLihVRQ=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], @@ -1366,7 +1366,7 @@ "fumadocs-mdx": ["fumadocs-mdx@14.2.2", "", { "dependencies": { "@mdx-js/mdx": "^3.1.1", "@standard-schema/spec": "^1.1.0", "chokidar": "^5.0.0", "esbuild": "^0.27.2", "estree-util-value-to-estree": "^3.5.0", "js-yaml": "^4.1.1", "mdast-util-to-markdown": "^2.1.2", "picocolors": "^1.1.1", "picomatch": "^4.0.3", "remark-mdx": "^3.1.1", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", "zod": "^4.2.1" }, "peerDependencies": { "@fumadocs/mdx-remote": "^1.4.0", "@types/react": "*", "fumadocs-core": "^15.0.0 || ^16.0.0", "next": "^15.3.0 || ^16.0.0", "react": "*", "vite": "6.x.x || 7.x.x" }, "optionalPeers": ["@fumadocs/mdx-remote", "@types/react", "next", "react", "vite"], "bin": { "fumadocs-mdx": "dist/bin.js" } }, "sha512-ClRjZd5WloWhbNHcUgA0PvbiTFuVKjv+qmrfF4aAeHzae6jeZtm1dvSYZ6iKoEHjFz6bt3UqiCgLHAUuXS8ifg=="], - "fumadocs-ui": ["@fumadocs/base-ui@16.4.10", "", { "dependencies": { "@base-ui/react": "^1.1.0", "@fumadocs/ui": "16.4.10", "class-variance-authority": "^0.7.1", "lucide-react": "^0.563.0", "next-themes": "^0.4.6", "react-medium-image-zoom": "^5.4.0", "scroll-into-view-if-needed": "^3.1.0" }, "peerDependencies": { "@types/react": "*", "fumadocs-core": "16.4.10", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.0.0" }, "optionalPeers": ["@types/react", "next", "tailwindcss"] }, "sha512-rzycQpTONDankuiYS8DYHSrWdP3xxeYnbokav2x2kEDmgnLL9ZKYA661usOXwM4f08YTRcA87ozRuMCUe7K5UA=="], + "fumadocs-ui": ["@fumadocs/base-ui@16.4.11", "", { "dependencies": { "@base-ui/react": "^1.1.0", "@fumadocs/ui": "16.4.11", "class-variance-authority": "^0.7.1", "lucide-react": "^0.563.0", "next-themes": "^0.4.6", "react-medium-image-zoom": "^5.4.0", "scroll-into-view-if-needed": "^3.1.0" }, "peerDependencies": { "@types/react": "*", "fumadocs-core": "16.4.11", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.0.0" }, "optionalPeers": ["@types/react", "next", "tailwindcss"] }, "sha512-u8tdgEJnjnU2fFJ5HhisL0IP6j9sGyonmUpG3X6FbRw/domwlcZV2H4Cz9/Usqwqvv7b/pc4F9hTxMJm6PT7lg=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], diff --git a/packages/template-generator/src/template-handlers/addons.ts b/packages/template-generator/src/template-handlers/addons.ts index eb13e515a..cba955f11 100644 --- a/packages/template-generator/src/template-handlers/addons.ts +++ b/packages/template-generator/src/template-handlers/addons.ts @@ -28,6 +28,54 @@ export async function processAddonTemplates( continue; } + if (addon === "docker-compose") { + // Place docker-compose.yml at project root + processTemplatesFromPrefix(vfs, templates, "addons/docker-compose", "", config); + + // Place server Dockerfile if backend exists + if (config.backend !== "self" && config.backend !== "none") { + processTemplatesFromPrefix( + vfs, + templates, + "addons/docker-compose/apps/server", + "apps/server", + config, + ); + } + + // Place web Dockerfile based on frontend + if (config.frontend.includes("next")) { + processTemplatesFromPrefix( + vfs, + templates, + "addons/docker-compose/apps/web", + "apps/web", + config, + ); + } else if ( + config.frontend.some((f) => + [ + "tanstack-router", + "react-router", + "tanstack-start", + "solid", + "svelte", + "nuxt", + "astro", + ].includes(f), + ) + ) { + processTemplatesFromPrefix( + vfs, + templates, + "addons/docker-compose/apps/web", + "apps/web", + config, + ); + } + continue; + } + processTemplatesFromPrefix(vfs, templates, `addons/${addon}`, "", config); } } diff --git a/packages/template-generator/src/templates.generated.ts b/packages/template-generator/src/templates.generated.ts index 2b827006b..e9256ca25 100644 --- a/packages/template-generator/src/templates.generated.ts +++ b/packages/template-generator/src/templates.generated.ts @@ -98,6 +98,378 @@ export const EMBEDDED_TEMPLATES: Map = new Map([ ] {{/if}} } +`], + ["addons/docker-compose/.dockerignore.hbs", `node_modules +.git +.env* +*.log +dist +.next +.turbo +`], + ["addons/docker-compose/apps/server/.dockerignore.hbs", `node_modules +.git +.gitignore +.env +.env.* +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +dist +.turbo +.DS_Store +`], + ["addons/docker-compose/apps/server/Dockerfile.hbs", `{{#if (eq runtime "bun")}} +FROM oven/bun:1 AS base +WORKDIR /app + +FROM base AS deps +COPY package.json bun.lock* ./ +RUN bun install --frozen-lockfile + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN bun run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./ +EXPOSE 3001 +CMD ["bun", "run", "dist/index.js"] +{{else}} +FROM node:20-alpine AS base +WORKDIR /app + +FROM base AS deps +COPY package.json package-lock.json* ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./ +EXPOSE 3001 +CMD ["node", "dist/index.js"] +{{/if}} +`], + ["addons/docker-compose/apps/web/.dockerignore.hbs", `node_modules +.git +.gitignore +.env +.env.* +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +dist +.next +.turbo +.DS_Store +`], + ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{#if (includes frontend "next")}} +FROM node:20-alpine AS base +WORKDIR /app + +FROM base AS deps +COPY package.json package-lock.json* ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{else}} +FROM node:20-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM nginx:alpine AS runner +COPY --from=builder /app/dist /usr/share/nginx/html +EXPOSE 3000 +CMD ["nginx", "-g", "daemon off;"] +{{/if}} +`], + ["addons/docker-compose/apps/web/Dockerfile.next.hbs", `{{#if (eq packageManager "bun")}} +FROM oven/bun:1 AS base +WORKDIR /app + +FROM base AS deps +COPY package.json bun.lock* ./ +RUN bun install --frozen-lockfile + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN bun run build + +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{else if (eq packageManager "pnpm")}} +FROM node:20-alpine AS base +RUN corepack enable && corepack prepare pnpm@latest --activate +WORKDIR /app + +FROM base AS deps +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{else}} +FROM node:20-alpine AS base +WORKDIR /app + +FROM base AS deps +COPY package.json package-lock.json* ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{/if}} +`], + ["addons/docker-compose/apps/web/Dockerfile.vite.hbs", `{{#if (eq packageManager "bun")}} +FROM oven/bun:1 AS builder +WORKDIR /app +COPY package.json bun.lock* ./ +RUN bun install --frozen-lockfile +COPY . . +RUN bun run build +{{else if (eq packageManager "pnpm")}} +FROM node:20-alpine AS builder +RUN corepack enable && corepack prepare pnpm@latest --activate +WORKDIR /app +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install --frozen-lockfile +COPY . . +RUN pnpm run build +{{else}} +FROM node:20-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci +COPY . . +RUN npm run build +{{/if}} + +FROM nginx:alpine AS runner +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 3000 +CMD ["nginx", "-g", "daemon off;"] +`], + ["addons/docker-compose/apps/web/nginx.conf.hbs", `server { + listen 3000; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript; + + # Handle SPA routing + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} +`], + ["addons/docker-compose/docker-compose.yml.hbs", `name: {{projectName}} + +services: + web: + build: + context: ./apps/web + dockerfile: Dockerfile + ports: + - "3000:3000" + environment: + - NODE_ENV=production +{{#unless (eq backend "self")}} +{{#unless (eq backend "none")}} + depends_on: + - server +{{/unless}} +{{/unless}} +{{#unless (eq database "none")}} +{{#if (eq backend "self")}} + depends_on: + - db +{{/if}} +{{/unless}} + restart: unless-stopped + +{{#unless (eq backend "self")}} +{{#unless (eq backend "none")}} + server: + build: + context: ./apps/server + dockerfile: Dockerfile + ports: + - "3001:3001" + environment: + - NODE_ENV=production +{{#unless (eq database "none")}} + - DATABASE_URL=\${DATABASE_URL} + depends_on: + db: + condition: service_healthy +{{/unless}} + restart: unless-stopped +{{/unless}} +{{/unless}} + +{{#if (eq database "postgres")}} + db: + image: postgres:16-alpine + container_name: {{projectName}}-postgres + environment: + POSTGRES_DB: {{projectName}} + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + postgres_data: +{{/if}} +{{#if (eq database "mysql")}} + db: + image: mysql:8 + container_name: {{projectName}}-mysql + environment: + MYSQL_DATABASE: {{projectName}} + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: rootpassword + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + mysql_data: +{{/if}} +{{#if (eq database "mongodb")}} + db: + image: mongo:7 + container_name: {{projectName}}-mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: password + MONGO_INITDB_DATABASE: {{projectName}} + ports: + - "27017:27017" + volumes: + - mongo_data:/data/db + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + mongo_data: +{{/if}} `], ["addons/husky/.husky/pre-commit", `lint-staged `], @@ -25568,4 +25940,4 @@ function SuccessPage() { `] ]); -export const TEMPLATE_COUNT = 435; +export const TEMPLATE_COUNT = 444; diff --git a/packages/template-generator/templates/addons/docker-compose/.dockerignore.hbs b/packages/template-generator/templates/addons/docker-compose/.dockerignore.hbs new file mode 100644 index 000000000..8c856970f --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/.dockerignore.hbs @@ -0,0 +1,7 @@ +node_modules +.git +.env* +*.log +dist +.next +.turbo diff --git a/packages/template-generator/templates/addons/docker-compose/apps/server/.dockerignore.hbs b/packages/template-generator/templates/addons/docker-compose/apps/server/.dockerignore.hbs new file mode 100644 index 000000000..3cc9c2f59 --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/apps/server/.dockerignore.hbs @@ -0,0 +1,12 @@ +node_modules +.git +.gitignore +.env +.env.* +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +dist +.turbo +.DS_Store diff --git a/packages/template-generator/templates/addons/docker-compose/apps/server/Dockerfile.hbs b/packages/template-generator/templates/addons/docker-compose/apps/server/Dockerfile.hbs new file mode 100644 index 000000000..9f9992426 --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/apps/server/Dockerfile.hbs @@ -0,0 +1,43 @@ +{{#if (eq runtime "bun")}} +FROM oven/bun:1 AS base +WORKDIR /app + +FROM base AS deps +COPY package.json bun.lock* ./ +RUN bun install --frozen-lockfile + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN bun run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./ +EXPOSE 3001 +CMD ["bun", "run", "dist/index.js"] +{{else}} +FROM node:20-alpine AS base +WORKDIR /app + +FROM base AS deps +COPY package.json package-lock.json* ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./ +EXPOSE 3001 +CMD ["node", "dist/index.js"] +{{/if}} diff --git a/packages/template-generator/templates/addons/docker-compose/apps/web/.dockerignore.hbs b/packages/template-generator/templates/addons/docker-compose/apps/web/.dockerignore.hbs new file mode 100644 index 000000000..6a21877bd --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/apps/web/.dockerignore.hbs @@ -0,0 +1,13 @@ +node_modules +.git +.gitignore +.env +.env.* +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +dist +.next +.turbo +.DS_Store diff --git a/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs new file mode 100644 index 000000000..c92a171a9 --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs @@ -0,0 +1,40 @@ +{{#if (includes frontend "next")}} +FROM node:20-alpine AS base +WORKDIR /app + +FROM base AS deps +COPY package.json package-lock.json* ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{else}} +FROM node:20-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM nginx:alpine AS runner +COPY --from=builder /app/dist /usr/share/nginx/html +EXPOSE 3000 +CMD ["nginx", "-g", "daemon off;"] +{{/if}} diff --git a/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.next.hbs b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.next.hbs new file mode 100644 index 000000000..3b35f8a96 --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.next.hbs @@ -0,0 +1,83 @@ +{{#if (eq packageManager "bun")}} +FROM oven/bun:1 AS base +WORKDIR /app + +FROM base AS deps +COPY package.json bun.lock* ./ +RUN bun install --frozen-lockfile + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN bun run build + +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{else if (eq packageManager "pnpm")}} +FROM node:20-alpine AS base +RUN corepack enable && corepack prepare pnpm@latest --activate +WORKDIR /app + +FROM base AS deps +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{else}} +FROM node:20-alpine AS base +WORKDIR /app + +FROM base AS deps +COPY package.json package-lock.json* ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "server.js"] +{{/if}} diff --git a/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.vite.hbs b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.vite.hbs new file mode 100644 index 000000000..bf0f6d16e --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.vite.hbs @@ -0,0 +1,29 @@ +{{#if (eq packageManager "bun")}} +FROM oven/bun:1 AS builder +WORKDIR /app +COPY package.json bun.lock* ./ +RUN bun install --frozen-lockfile +COPY . . +RUN bun run build +{{else if (eq packageManager "pnpm")}} +FROM node:20-alpine AS builder +RUN corepack enable && corepack prepare pnpm@latest --activate +WORKDIR /app +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install --frozen-lockfile +COPY . . +RUN pnpm run build +{{else}} +FROM node:20-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci +COPY . . +RUN npm run build +{{/if}} + +FROM nginx:alpine AS runner +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 3000 +CMD ["nginx", "-g", "daemon off;"] diff --git a/packages/template-generator/templates/addons/docker-compose/apps/web/nginx.conf.hbs b/packages/template-generator/templates/addons/docker-compose/apps/web/nginx.conf.hbs new file mode 100644 index 000000000..c942837a6 --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/apps/web/nginx.conf.hbs @@ -0,0 +1,24 @@ +server { + listen 3000; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript; + + # Handle SPA routing + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/packages/template-generator/templates/addons/docker-compose/docker-compose.yml.hbs b/packages/template-generator/templates/addons/docker-compose/docker-compose.yml.hbs new file mode 100644 index 000000000..541c0f4eb --- /dev/null +++ b/packages/template-generator/templates/addons/docker-compose/docker-compose.yml.hbs @@ -0,0 +1,112 @@ +name: {{projectName}} + +services: + web: + build: + context: ./apps/web + dockerfile: Dockerfile + ports: + - "3000:3000" + environment: + - NODE_ENV=production +{{#unless (eq backend "self")}} +{{#unless (eq backend "none")}} + depends_on: + - server +{{/unless}} +{{/unless}} +{{#unless (eq database "none")}} +{{#if (eq backend "self")}} + depends_on: + - db +{{/if}} +{{/unless}} + restart: unless-stopped + +{{#unless (eq backend "self")}} +{{#unless (eq backend "none")}} + server: + build: + context: ./apps/server + dockerfile: Dockerfile + ports: + - "3001:3001" + environment: + - NODE_ENV=production +{{#unless (eq database "none")}} + - DATABASE_URL=${DATABASE_URL} + depends_on: + db: + condition: service_healthy +{{/unless}} + restart: unless-stopped +{{/unless}} +{{/unless}} + +{{#if (eq database "postgres")}} + db: + image: postgres:16-alpine + container_name: {{projectName}}-postgres + environment: + POSTGRES_DB: {{projectName}} + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + postgres_data: +{{/if}} +{{#if (eq database "mysql")}} + db: + image: mysql:8 + container_name: {{projectName}}-mysql + environment: + MYSQL_DATABASE: {{projectName}} + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: rootpassword + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + mysql_data: +{{/if}} +{{#if (eq database "mongodb")}} + db: + image: mongo:7 + container_name: {{projectName}}-mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: password + MONGO_INITDB_DATABASE: {{projectName}} + ports: + - "27017:27017" + volumes: + - mongo_data:/data/db + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + mongo_data: +{{/if}} diff --git a/packages/types/src/schemas.ts b/packages/types/src/schemas.ts index d47a0553b..90839350d 100644 --- a/packages/types/src/schemas.ts +++ b/packages/types/src/schemas.ts @@ -46,6 +46,7 @@ export const AddonsSchema = z "oxlint", "opentui", "wxt", + "docker-compose", "skills", "none", ]) From 5a04f437e31ae5e3271a0efeaba0a246db2102b9 Mon Sep 17 00:00:00 2001 From: Crimson341 <150315417+Crimson341@users.noreply.github.com> Date: Sat, 31 Jan 2026 16:44:17 -0500 Subject: [PATCH 2/4] Update packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../templates/addons/docker-compose/apps/web/Dockerfile.hbs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs index c92a171a9..f8b814fdb 100644 --- a/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs +++ b/packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.hbs @@ -35,6 +35,7 @@ RUN npm run build FROM nginx:alpine AS runner COPY --from=builder /app/dist /usr/share/nginx/html -EXPOSE 3000 +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf CMD ["nginx", "-g", "daemon off;"] {{/if}} From cffc02553b59876c243e2cce53fe09de62f452e5 Mon Sep 17 00:00:00 2001 From: Crimson341 <150315417+Crimson341@users.noreply.github.com> Date: Sat, 31 Jan 2026 16:44:38 -0500 Subject: [PATCH 3/4] Update apps/cli/test/addons.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/cli/test/addons.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli/test/addons.test.ts b/apps/cli/test/addons.test.ts index b0c4cd6fe..80722391d 100644 --- a/apps/cli/test/addons.test.ts +++ b/apps/cli/test/addons.test.ts @@ -251,7 +251,7 @@ describe("Addon Configurations", () => { api: "trpc", examples: ["none"], dbSetup: "none", - webDeploy: "cloudflare", + serverDeploy: "cloudflare", serverDeploy: "alchemy", // Workers runtime requires server deployment expectError: true, }); From f9d2225c1907eac8b032c8628af3dc0935f59fe3 Mon Sep 17 00:00:00 2001 From: Crimson341 <150315417+Crimson341@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:18:38 -0500 Subject: [PATCH 4/4] Update packages/template-generator/src/templates.generated.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/template-generator/src/templates.generated.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/template-generator/src/templates.generated.ts b/packages/template-generator/src/templates.generated.ts index e9256ca25..9ec91f0d9 100644 --- a/packages/template-generator/src/templates.generated.ts +++ b/packages/template-generator/src/templates.generated.ts @@ -178,7 +178,7 @@ dist .turbo .DS_Store `], - ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{#if (includes frontend "next")}} + ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{`#if` (includes frontend "next")}} FROM node:20-alpine AS base WORKDIR /app @@ -215,6 +215,7 @@ RUN npm run build FROM nginx:alpine AS runner COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 3000 CMD ["nginx", "-g", "daemon off;"] {{/if}}