mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Docker Compose Database Setup (#379)
Co-authored-by: Aman Varshney <amanvarshney.work@gmail.com>
This commit is contained in:
5
.changeset/seven-cobras-design.md
Normal file
5
.changeset/seven-cobras-design.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
Added support for local database setup using Docker Compose for PostgreSQL, MySQL, and MongoDB.
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { ProjectConfig, Frontend } from "./types";
|
||||
import type { Frontend, ProjectConfig } from "./types";
|
||||
import { getUserPkgManager } from "./utils/get-package-manager";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import path from "node:path";
|
||||
import type { Database, ProjectConfig } from "../../types";
|
||||
import {
|
||||
addEnvVariablesToFile,
|
||||
type EnvVariable,
|
||||
} from "../project-generation/env-setup";
|
||||
|
||||
export async function setupDockerCompose(config: ProjectConfig): Promise<void> {
|
||||
const { database, projectDir, projectName } = config;
|
||||
|
||||
if (database === "none" || database === "sqlite") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await writeEnvFile(projectDir, database, projectName);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeEnvFile(
|
||||
projectDir: string,
|
||||
database: Database,
|
||||
projectName: string,
|
||||
) {
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
const variables: EnvVariable[] = [
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value: getDatabaseUrl(database, projectName),
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
await addEnvVariablesToFile(envPath, variables);
|
||||
}
|
||||
|
||||
function getDatabaseUrl(database: Database, projectName: string): string {
|
||||
switch (database) {
|
||||
case "postgres":
|
||||
return `postgresql://postgres:password@localhost:5432/${projectName}`;
|
||||
case "mysql":
|
||||
return `mysql://user:password@localhost:3306/${projectName}`;
|
||||
case "mongodb":
|
||||
return `mongodb://root:password@localhost:27017/${projectName}?authSource=admin`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
setupBackendFramework,
|
||||
setupDbOrmTemplates,
|
||||
setupDeploymentTemplates,
|
||||
setupDockerComposeTemplates,
|
||||
setupExamplesTemplate,
|
||||
setupFrontendTemplates,
|
||||
} from "./template-manager";
|
||||
@@ -44,6 +45,7 @@ export async function createProject(options: ProjectConfig) {
|
||||
await setupBackendFramework(projectDir, options);
|
||||
if (!isConvex) {
|
||||
await setupDbOrmTemplates(projectDir, options);
|
||||
await setupDockerComposeTemplates(projectDir, options);
|
||||
await setupAuthTemplate(projectDir, options);
|
||||
}
|
||||
if (options.examples.length > 0 && options.examples[0] !== "none") {
|
||||
@@ -94,7 +96,7 @@ export async function createProject(options: ProjectConfig) {
|
||||
|
||||
await initializeGit(projectDir, options.git);
|
||||
|
||||
displayPostInstallInstructions({
|
||||
await displayPostInstallInstructions({
|
||||
...options,
|
||||
depsInstalled: options.install,
|
||||
});
|
||||
|
||||
@@ -187,7 +187,8 @@ export async function setupEnvironmentVariables(
|
||||
dbSetup === "mongodb-atlas" ||
|
||||
dbSetup === "neon" ||
|
||||
dbSetup === "supabase" ||
|
||||
dbSetup === "d1";
|
||||
dbSetup === "d1" ||
|
||||
dbSetup === "docker";
|
||||
|
||||
if (database !== "none" && !specializedSetup) {
|
||||
switch (database) {
|
||||
|
||||
@@ -7,9 +7,10 @@ import type {
|
||||
ProjectConfig,
|
||||
Runtime,
|
||||
} from "../../types";
|
||||
import { getDockerStatus } from "../../utils/docker-utils";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
|
||||
export function displayPostInstallInstructions(
|
||||
export async function displayPostInstallInstructions(
|
||||
config: ProjectConfig & { depsInstalled: boolean },
|
||||
) {
|
||||
const {
|
||||
@@ -34,7 +35,7 @@ export function displayPostInstallInstructions(
|
||||
|
||||
const databaseInstructions =
|
||||
!isConvex && database !== "none"
|
||||
? getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
|
||||
? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
|
||||
: "";
|
||||
|
||||
const tauriInstructions = addons?.includes("tauri")
|
||||
@@ -193,15 +194,24 @@ function getLintingInstructions(runCmd?: string): string {
|
||||
)} Format and lint fix: ${`${runCmd} check`}\n`;
|
||||
}
|
||||
|
||||
function getDatabaseInstructions(
|
||||
async function getDatabaseInstructions(
|
||||
database: Database,
|
||||
orm?: ORM,
|
||||
runCmd?: string,
|
||||
runtime?: Runtime,
|
||||
dbSetup?: DatabaseSetup,
|
||||
): string {
|
||||
): Promise<string> {
|
||||
const instructions = [];
|
||||
|
||||
if (dbSetup === "docker") {
|
||||
const dockerStatus = await getDockerStatus(database);
|
||||
|
||||
if (dockerStatus.message) {
|
||||
instructions.push(dockerStatus.message);
|
||||
instructions.push("");
|
||||
}
|
||||
}
|
||||
|
||||
if (runtime === "workers" && dbSetup === "d1") {
|
||||
const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
|
||||
|
||||
@@ -255,10 +265,26 @@ function getDatabaseInstructions(
|
||||
)} Prisma with Bun may require additional configuration. If you encounter errors,\nfollow the guidance provided in the error messages`,
|
||||
);
|
||||
}
|
||||
|
||||
if (database === "mongodb" && dbSetup === "docker") {
|
||||
instructions.push(
|
||||
`${pc.yellow(
|
||||
"WARNING:",
|
||||
)} Prisma + MongoDB + Docker combination may not work.`,
|
||||
);
|
||||
}
|
||||
if (dbSetup === "docker") {
|
||||
instructions.push(
|
||||
`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`,
|
||||
);
|
||||
}
|
||||
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
||||
instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
||||
} else if (orm === "drizzle") {
|
||||
if (dbSetup === "docker") {
|
||||
instructions.push(
|
||||
`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`,
|
||||
);
|
||||
}
|
||||
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
||||
instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
||||
if (database === "sqlite" && dbSetup !== "d1") {
|
||||
@@ -268,6 +294,12 @@ function getDatabaseInstructions(
|
||||
)} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`,
|
||||
);
|
||||
}
|
||||
} else if (orm === "mongoose") {
|
||||
if (dbSetup === "docker") {
|
||||
instructions.push(
|
||||
`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`,
|
||||
);
|
||||
}
|
||||
} else if (orm === "none") {
|
||||
instructions.push(
|
||||
`${pc.yellow("NOTE:")} Manual database schema setup required.`,
|
||||
|
||||
@@ -80,6 +80,12 @@ async function updateRootPackageJson(
|
||||
scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
|
||||
}
|
||||
}
|
||||
if (options.dbSetup === "docker") {
|
||||
scripts["db:start"] = `turbo -F ${backendPackageName} db:start`;
|
||||
scripts["db:watch"] = `turbo -F ${backendPackageName} db:watch`;
|
||||
scripts["db:stop"] = `turbo -F ${backendPackageName} db:stop`;
|
||||
scripts["db:down"] = `turbo -F ${backendPackageName} db:down`;
|
||||
}
|
||||
} else if (options.packageManager === "pnpm") {
|
||||
scripts.dev = devScript;
|
||||
scripts.build = "pnpm -r build";
|
||||
@@ -105,6 +111,12 @@ async function updateRootPackageJson(
|
||||
`pnpm --filter ${backendPackageName} db:migrate`;
|
||||
}
|
||||
}
|
||||
if (options.dbSetup === "docker") {
|
||||
scripts["db:start"] = `pnpm --filter ${backendPackageName} db:start`;
|
||||
scripts["db:watch"] = `pnpm --filter ${backendPackageName} db:watch`;
|
||||
scripts["db:stop"] = `pnpm --filter ${backendPackageName} db:stop`;
|
||||
scripts["db:down"] = `pnpm --filter ${backendPackageName} db:down`;
|
||||
}
|
||||
} else if (options.packageManager === "npm") {
|
||||
scripts.dev = devScript;
|
||||
scripts.build = "npm run build --workspaces";
|
||||
@@ -132,6 +144,14 @@ async function updateRootPackageJson(
|
||||
`npm run db:migrate --workspace ${backendPackageName}`;
|
||||
}
|
||||
}
|
||||
if (options.dbSetup === "docker") {
|
||||
scripts["db:start"] =
|
||||
`npm run db:start --workspace ${backendPackageName}`;
|
||||
scripts["db:watch"] =
|
||||
`npm run db:watch --workspace ${backendPackageName}`;
|
||||
scripts["db:stop"] = `npm run db:stop --workspace ${backendPackageName}`;
|
||||
scripts["db:down"] = `npm run db:down --workspace ${backendPackageName}`;
|
||||
}
|
||||
} else if (options.packageManager === "bun") {
|
||||
scripts.dev = devScript;
|
||||
scripts.build = "bun run --filter '*' build";
|
||||
@@ -157,6 +177,12 @@ async function updateRootPackageJson(
|
||||
`bun run --filter ${backendPackageName} db:migrate`;
|
||||
}
|
||||
}
|
||||
if (options.dbSetup === "docker") {
|
||||
scripts["db:start"] = `bun run --filter ${backendPackageName} db:start`;
|
||||
scripts["db:watch"] = `bun run --filter ${backendPackageName} db:watch`;
|
||||
scripts["db:stop"] = `bun run --filter ${backendPackageName} db:stop`;
|
||||
scripts["db:down"] = `bun run --filter ${backendPackageName} db:down`;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.addons.includes("biome")) {
|
||||
@@ -246,6 +272,13 @@ async function updateServerPackageJson(
|
||||
}
|
||||
}
|
||||
|
||||
if (options.dbSetup === "docker") {
|
||||
scripts["db:start"] = "docker compose up -d";
|
||||
scripts["db:watch"] = "docker compose up";
|
||||
scripts["db:stop"] = "docker compose stop";
|
||||
scripts["db:down"] = "docker compose down";
|
||||
}
|
||||
|
||||
await fs.writeJson(serverPackageJsonPath, serverPackageJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
|
||||
@@ -833,6 +833,26 @@ export async function handleExtras(
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupDockerComposeTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
if (context.dbSetup !== "docker" || context.database === "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverAppDir = path.join(projectDir, "apps/server");
|
||||
const dockerSrcDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/db-setup/docker-compose/${context.database}`,
|
||||
);
|
||||
|
||||
if (await fs.pathExists(dockerSrcDir)) {
|
||||
await processAndCopyFiles("**/*", dockerSrcDir, serverAppDir, context);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupDeploymentTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
|
||||
@@ -6,6 +6,7 @@ import pc from "picocolors";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { setupCloudflareD1 } from "../database-providers/d1-setup";
|
||||
import { setupDockerCompose } from "../database-providers/docker-compose-setup";
|
||||
import { setupMongoDBAtlas } from "../database-providers/mongodb-atlas-setup";
|
||||
import { setupNeonPostgres } from "../database-providers/neon-setup";
|
||||
import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup";
|
||||
@@ -76,7 +77,9 @@ export async function setupDatabase(config: ProjectConfig): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
if (database === "sqlite" && dbSetup === "turso") {
|
||||
if (dbSetup === "docker") {
|
||||
await setupDockerCompose(config);
|
||||
} else if (database === "sqlite" && dbSetup === "turso") {
|
||||
await setupTurso(config);
|
||||
} else if (database === "sqlite" && dbSetup === "d1") {
|
||||
await setupCloudflareD1(config);
|
||||
|
||||
@@ -65,6 +65,20 @@ export async function getDBSetupChoice(
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
value: "docker" as const,
|
||||
label: "Docker",
|
||||
hint: "Run locally with docker compose",
|
||||
},
|
||||
{ value: "none" as const, label: "None", hint: "Manual setup" },
|
||||
];
|
||||
} else if (databaseType === "mysql") {
|
||||
options = [
|
||||
{
|
||||
value: "docker" as const,
|
||||
label: "Docker",
|
||||
hint: "Run locally with docker compose",
|
||||
},
|
||||
{ value: "none" as const, label: "None", hint: "Manual setup" },
|
||||
];
|
||||
} else if (databaseType === "mongodb") {
|
||||
@@ -74,6 +88,11 @@ export async function getDBSetupChoice(
|
||||
label: "MongoDB Atlas",
|
||||
hint: "The most effective way to deploy MongoDB",
|
||||
},
|
||||
{
|
||||
value: "docker" as const,
|
||||
label: "Docker",
|
||||
hint: "Run locally with docker compose",
|
||||
},
|
||||
{ value: "none" as const, label: "None", hint: "Manual setup" },
|
||||
];
|
||||
} else {
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function getDeploymentChoice(
|
||||
{
|
||||
value: "workers",
|
||||
label: "Cloudflare Workers",
|
||||
hint: "Deploy to Cloudflare Workers using Wrangler",
|
||||
hint: "Deploy to Cloudflare Workers using Wrangler",
|
||||
},
|
||||
{ value: "none", label: "None", hint: "Manual setup" },
|
||||
];
|
||||
|
||||
@@ -61,6 +61,7 @@ export const DatabaseSetupSchema = z
|
||||
"mongodb-atlas",
|
||||
"supabase",
|
||||
"d1",
|
||||
"docker",
|
||||
"none",
|
||||
])
|
||||
.describe("Database hosting setup");
|
||||
|
||||
82
apps/cli/src/utils/docker-utils.ts
Normal file
82
apps/cli/src/utils/docker-utils.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import os from "node:os";
|
||||
import pc from "picocolors";
|
||||
import type { Database } from "../types";
|
||||
import { commandExists } from "./command-exists";
|
||||
|
||||
export async function isDockerInstalled(): Promise<boolean> {
|
||||
return commandExists("docker");
|
||||
}
|
||||
|
||||
export async function isDockerRunning(): Promise<boolean> {
|
||||
try {
|
||||
const { $ } = await import("execa");
|
||||
await $`docker info`;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDockerInstallInstructions(
|
||||
platform: string,
|
||||
database: Database,
|
||||
): string {
|
||||
const isMac = platform === "darwin";
|
||||
const isWindows = platform === "win32";
|
||||
const isLinux = platform === "linux";
|
||||
|
||||
let installUrl = "";
|
||||
let platformName = "";
|
||||
|
||||
if (isMac) {
|
||||
installUrl = "https://docs.docker.com/desktop/setup/install/mac-install/";
|
||||
platformName = "macOS";
|
||||
} else if (isWindows) {
|
||||
installUrl =
|
||||
"https://docs.docker.com/desktop/setup/install/windows-install/";
|
||||
platformName = "Windows";
|
||||
} else if (isLinux) {
|
||||
installUrl = "https://docs.docker.com/desktop/setup/install/linux/";
|
||||
platformName = "Linux";
|
||||
}
|
||||
|
||||
const databaseName =
|
||||
database === "mongodb"
|
||||
? "MongoDB"
|
||||
: database === "mysql"
|
||||
? "MySQL"
|
||||
: "PostgreSQL";
|
||||
|
||||
return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
|
||||
}
|
||||
|
||||
export async function getDockerStatus(database: Database): Promise<{
|
||||
installed: boolean;
|
||||
running: boolean;
|
||||
message?: string;
|
||||
}> {
|
||||
const platform = os.platform();
|
||||
const installed = await isDockerInstalled();
|
||||
|
||||
if (!installed) {
|
||||
return {
|
||||
installed: false,
|
||||
running: false,
|
||||
message: getDockerInstallInstructions(platform, database),
|
||||
};
|
||||
}
|
||||
|
||||
const running = await isDockerRunning();
|
||||
if (!running) {
|
||||
return {
|
||||
installed: true,
|
||||
running: false,
|
||||
message: `${pc.yellow("IMPORTANT:")} Docker is installed but not running.`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
installed: true,
|
||||
running: true,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from "node:path";
|
||||
import { consola } from "consola";
|
||||
import { WEB_FRAMEWORKS } from "./constants";
|
||||
import {
|
||||
type Addons,
|
||||
type API,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
type Runtime,
|
||||
type WebDeploy,
|
||||
} from "./types";
|
||||
import { WEB_FRAMEWORKS } from "./constants";
|
||||
|
||||
export function processAndValidateFlags(
|
||||
options: CLIInput,
|
||||
@@ -373,6 +373,20 @@ export function processAndValidateFlags(
|
||||
}
|
||||
}
|
||||
|
||||
if (config.dbSetup === "docker" && config.database === "sqlite") {
|
||||
consola.fatal(
|
||||
"Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (config.dbSetup === "docker" && config.runtime === "workers") {
|
||||
consola.fatal(
|
||||
"Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
providedFlags.has("runtime") &&
|
||||
options.runtime === "workers" &&
|
||||
@@ -434,6 +448,17 @@ export function processAndValidateFlags(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
providedFlags.has("runtime") &&
|
||||
options.runtime === "workers" &&
|
||||
config.dbSetup === "docker"
|
||||
) {
|
||||
consola.fatal(
|
||||
"Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
providedFlags.has("database") &&
|
||||
config.database === "mongodb" &&
|
||||
@@ -445,6 +470,17 @@ export function processAndValidateFlags(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
providedFlags.has("db-setup") &&
|
||||
options.dbSetup === "docker" &&
|
||||
config.runtime === "workers"
|
||||
) {
|
||||
consola.fatal(
|
||||
"Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hasWebFrontendFlag = (config.frontend ?? []).some((f) =>
|
||||
WEB_FRAMEWORKS.includes(f),
|
||||
);
|
||||
@@ -499,6 +535,13 @@ export function validateConfigCompatibility(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (effectiveRuntime === "workers" && config.dbSetup === "docker") {
|
||||
consola.fatal(
|
||||
"Cloudflare Workers runtime is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use a different runtime or change to D1 database setup.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const includesNuxt = effectiveFrontend?.includes("nuxt");
|
||||
const includesSvelte = effectiveFrontend?.includes("svelte");
|
||||
const includesSolid = effectiveFrontend?.includes("solid");
|
||||
|
||||
@@ -39,5 +39,23 @@
|
||||
"persistent": true
|
||||
}
|
||||
{{/unless}}{{/if}}
|
||||
{{#if (eq dbSetup "docker")}},
|
||||
"db:start": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"db:stop": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"db:watch": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"db:down": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
}
|
||||
{{/if}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
name: {{projectName}}
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo
|
||||
container_name: {{projectName}}-mongodb
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: password
|
||||
MONGO_INITDB_DATABASE: {{projectName}}
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- {{projectName}}_mongodb_data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
{{projectName}}_mongodb_data:
|
||||
@@ -0,0 +1,24 @@
|
||||
name: {{projectName}}
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql
|
||||
container_name: {{projectName}}-mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
MYSQL_DATABASE: {{projectName}}
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- {{projectName}}_mysql_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
{{projectName}}_mysql_data:
|
||||
@@ -0,0 +1,23 @@
|
||||
name: {{projectName}}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
container_name: {{projectName}}-postgres
|
||||
environment:
|
||||
POSTGRES_DB: {{projectName}}
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: password
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- {{projectName}}_postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
{{projectName}}_postgres_data:
|
||||
4
apps/web/.gitignore
vendored
4
apps/web/.gitignore
vendored
@@ -19,10 +19,12 @@
|
||||
/.pnp
|
||||
.pnp.js
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# others
|
||||
.env*.local
|
||||
.vercel
|
||||
next-env.d.ts
|
||||
|
||||
analytics-data.json
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "bun run generate-analytics && bun run generate-schema && next build",
|
||||
"build": "next build",
|
||||
"dev": "next dev --turbopack",
|
||||
"start": "next start",
|
||||
"check": "biome check --write .",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
3
apps/web/public/icon/docker.svg
Normal file
3
apps/web/public/icon/docker.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#008fe2">
|
||||
<path d="M13.98 11.08h2.12a.19.19 0 0 0 .19-.19V9.01a.19.19 0 0 0-.19-.19h-2.12a.18.18 0 0 0-.18.18v1.9c0 .1.08.18.18.18m-2.95-5.43h2.12a.19.19 0 0 0 .18-.19V3.57a.19.19 0 0 0-.18-.18h-2.12a.18.18 0 0 0-.19.18v1.9c0 .1.09.18.19.18m0 2.71h2.12a.19.19 0 0 0 .18-.18V6.29a.19.19 0 0 0-.18-.18h-2.12a.18.18 0 0 0-.19.18v1.89c0 .1.09.18.19.18m-2.93 0h2.12a.19.19 0 0 0 .18-.18V6.29a.18.18 0 0 0-.18-.18H8.1a.18.18 0 0 0-.18.18v1.89c0 .1.08.18.18.18m-2.96 0h2.11a.19.19 0 0 0 .19-.18V6.29a.18.18 0 0 0-.19-.18H5.14a.19.19 0 0 0-.19.18v1.89c0 .1.08.18.19.18m5.89 2.72h2.12a.19.19 0 0 0 .18-.19V9.01a.19.19 0 0 0-.18-.19h-2.12a.18.18 0 0 0-.19.18v1.9c0 .1.09.18.19.18m-2.93 0h2.12a.18.18 0 0 0 .18-.19V9.01a.18.18 0 0 0-.18-.19H8.1a.18.18 0 0 0-.18.18v1.9c0 .1.08.18.18.18m-2.96 0h2.11a.18.18 0 0 0 .19-.19V9.01a.18.18 0 0 0-.18-.19H5.14a.19.19 0 0 0-.19.19v1.88c0 .1.08.19.19.19m-2.92 0h2.12a.18.18 0 0 0 .18-.19V9.01a.18.18 0 0 0-.18-.19H2.22a.18.18 0 0 0-.19.18v1.9c0 .1.08.18.19.18m21.54-1.19c-.06-.05-.67-.51-1.95-.51-.34 0-.68.03-1.01.09a3.77 3.77 0 0 0-1.72-2.57l-.34-.2-.23.33a4.6 4.6 0 0 0-.6 1.43c-.24.97-.1 1.88.4 2.66a4.7 4.7 0 0 1-1.75.42H.76a.75.75 0 0 0-.76.75 11.38 11.38 0 0 0 .7 4.06 6.03 6.03 0 0 0 2.4 3.12c1.18.73 3.1 1.14 5.28 1.14.98 0 1.96-.08 2.93-.26a12.25 12.25 0 0 0 3.82-1.4 10.5 10.5 0 0 0 2.61-2.13c1.25-1.42 2-3 2.55-4.4h.23c1.37 0 2.21-.55 2.68-1 .3-.3.55-.66.7-1.06l.1-.28Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -108,6 +108,7 @@
|
||||
"mongodb-atlas",
|
||||
"supabase",
|
||||
"d1",
|
||||
"docker",
|
||||
"none"
|
||||
],
|
||||
"description": "Database hosting setup"
|
||||
|
||||
@@ -600,6 +600,39 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
message: "Backend set to 'Hono' (required by Cloudflare D1)",
|
||||
});
|
||||
}
|
||||
} else if (nextStack.dbSetup === "docker") {
|
||||
if (nextStack.database === "sqlite") {
|
||||
notes.dbSetup.notes.push(
|
||||
"Docker setup is not needed for SQLite. It will be set to 'Basic Setup'.",
|
||||
);
|
||||
notes.dbSetup.hasIssue = true;
|
||||
notes.database.hasIssue = true;
|
||||
nextStack.dbSetup = "none";
|
||||
changed = true;
|
||||
changes.push({
|
||||
category: "dbSetup",
|
||||
message:
|
||||
"DB Setup set to 'Basic Setup' (SQLite doesn't need Docker)",
|
||||
});
|
||||
}
|
||||
|
||||
if (nextStack.runtime === "workers") {
|
||||
notes.dbSetup.notes.push(
|
||||
"Docker setup is not compatible with Cloudflare Workers runtime. Bun runtime will be selected.",
|
||||
);
|
||||
notes.runtime.notes.push(
|
||||
"Cloudflare Workers runtime does not support Docker setup. Bun runtime will be selected.",
|
||||
);
|
||||
notes.dbSetup.hasIssue = true;
|
||||
notes.runtime.hasIssue = true;
|
||||
nextStack.runtime = "bun";
|
||||
changed = true;
|
||||
changes.push({
|
||||
category: "dbSetup",
|
||||
message:
|
||||
"Runtime set to 'Bun' (Workers not compatible with Docker)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (nextStack.runtime === "workers") {
|
||||
@@ -654,6 +687,24 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
"Database set to 'SQLite' (MongoDB not compatible with Workers)",
|
||||
});
|
||||
}
|
||||
|
||||
if (nextStack.dbSetup === "docker") {
|
||||
notes.runtime.notes.push(
|
||||
"Cloudflare Workers runtime does not support Docker setup. D1 will be selected.",
|
||||
);
|
||||
notes.dbSetup.notes.push(
|
||||
"Docker setup is not compatible with Cloudflare Workers runtime. D1 will be selected.",
|
||||
);
|
||||
notes.runtime.hasIssue = true;
|
||||
notes.dbSetup.hasIssue = true;
|
||||
nextStack.dbSetup = "d1";
|
||||
changed = true;
|
||||
changes.push({
|
||||
category: "runtime",
|
||||
message:
|
||||
"DB Setup set to 'D1' (Docker not compatible with Workers)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isNuxt = nextStack.webFrontend.includes("nuxt");
|
||||
@@ -887,6 +938,7 @@ const generateCommand = (stackState: StackState): string => {
|
||||
"supabase",
|
||||
"prisma-postgres",
|
||||
"mongodb-atlas",
|
||||
"docker",
|
||||
].includes(stackState.dbSetup);
|
||||
|
||||
if (
|
||||
|
||||
@@ -195,15 +195,20 @@
|
||||
--spacing: 0.25rem;
|
||||
--shadow-2xs: 0px 4px 6px 0px hsl(240 30% 25% / 0.06);
|
||||
--shadow-xs: 0px 4px 6px 0px hsl(240 30% 25% / 0.06);
|
||||
--shadow-sm: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px
|
||||
--shadow-sm:
|
||||
0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px
|
||||
hsl(240 30% 25% / 0.12);
|
||||
--shadow: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px
|
||||
--shadow:
|
||||
0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px
|
||||
hsl(240 30% 25% / 0.12);
|
||||
--shadow-md: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px
|
||||
--shadow-md:
|
||||
0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px
|
||||
hsl(240 30% 25% / 0.12);
|
||||
--shadow-lg: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px
|
||||
--shadow-lg:
|
||||
0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px
|
||||
hsl(240 30% 25% / 0.12);
|
||||
--shadow-xl: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px
|
||||
--shadow-xl:
|
||||
0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px
|
||||
hsl(240 30% 25% / 0.12);
|
||||
--shadow-2xl: 0px 4px 6px 0px hsl(240 30% 25% / 0.3);
|
||||
--tracking-normal: 0em;
|
||||
@@ -258,15 +263,20 @@
|
||||
--spacing: 0.25rem;
|
||||
--shadow-2xs: 0px 2px 4px 0px hsl(240 30% 5% / 0.15);
|
||||
--shadow-xs: 0px 2px 4px 0px hsl(240 30% 5% / 0.15);
|
||||
--shadow-sm: 0px 4px 8px 0px hsl(240 30% 5% / 0.2), 0px 1px 2px -1px
|
||||
--shadow-sm:
|
||||
0px 4px 8px 0px hsl(240 30% 5% / 0.2), 0px 1px 2px -1px
|
||||
hsl(240 30% 5% / 0.15);
|
||||
--shadow: 0px 4px 8px 0px hsl(240 30% 5% / 0.2), 0px 1px 2px -1px
|
||||
--shadow:
|
||||
0px 4px 8px 0px hsl(240 30% 5% / 0.2), 0px 1px 2px -1px
|
||||
hsl(240 30% 5% / 0.15);
|
||||
--shadow-md: 0px 6px 12px 0px hsl(240 30% 5% / 0.25), 0px 2px 4px -1px
|
||||
--shadow-md:
|
||||
0px 6px 12px 0px hsl(240 30% 5% / 0.25), 0px 2px 4px -1px
|
||||
hsl(240 30% 5% / 0.2);
|
||||
--shadow-lg: 0px 8px 16px 0px hsl(240 30% 5% / 0.3), 0px 4px 6px -1px
|
||||
--shadow-lg:
|
||||
0px 8px 16px 0px hsl(240 30% 5% / 0.3), 0px 4px 6px -1px
|
||||
hsl(240 30% 5% / 0.25);
|
||||
--shadow-xl: 0px 12px 24px 0px hsl(240 30% 5% / 0.35), 0px 8px 10px -1px
|
||||
--shadow-xl:
|
||||
0px 12px 24px 0px hsl(240 30% 5% / 0.35), 0px 8px 10px -1px
|
||||
hsl(240 30% 5% / 0.3);
|
||||
--shadow-2xl: 0px 16px 32px 0px hsl(240 30% 5% / 0.4);
|
||||
}
|
||||
|
||||
@@ -311,6 +311,13 @@ export const TECH_OPTIONS = {
|
||||
icon: "/icon/supabase.svg",
|
||||
color: "from-emerald-400 to-emerald-600",
|
||||
},
|
||||
{
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
description: "Local database with Docker Compose",
|
||||
icon: "/icon/docker.svg",
|
||||
color: "from-blue-500 to-blue-700",
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "Basic Setup",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.1.1/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
|
||||
Reference in New Issue
Block a user