package cmd import "fmt" func gitignoreFor(stackType string) string { base := ".env\n.env.*\n!.env.example\n" switch stackType { case "node": return base + "node_modules/\ndist/\n.next/\n*.log\n" case "python": return base + "__pycache__/\n*.pyc\n.venv/\nvenv/\n*.egg-info/\ndist/\nbuild/\n" case "go": return base + "*.exe\n*.test\n*.out\n/vendor/\n" case "static": return base + "dist/\n.cache/\n" } return base } func dockerComposeTemplate(name, stackType string, port int) string { projectsDir := "~/projects" var svc string switch stackType { case "node": svc = fmt.Sprintf( " app:\n image: ${IMAGE:-ghcr.io/sketchferret/%s:latest}\n"+ " build: %s/%s\n"+ " restart: unless-stopped\n ports:\n - \"%d:3000\"\n env_file: .env\n", name, projectsDir, name, port) case "python": svc = fmt.Sprintf( " app:\n image: ${IMAGE:-ghcr.io/sketchferret/%s:latest}\n"+ " build: %s/%s\n"+ " restart: unless-stopped\n ports:\n - \"%d:8000\"\n env_file: .env\n", name, projectsDir, name, port) case "go": svc = fmt.Sprintf( " app:\n image: ${IMAGE:-ghcr.io/sketchferret/%s:latest}\n"+ " build: %s/%s\n"+ " restart: unless-stopped\n ports:\n - \"%d:8080\"\n env_file: .env\n", name, projectsDir, name, port) case "static": svc = fmt.Sprintf( " web:\n image: nginx:alpine\n restart: unless-stopped\n"+ " ports:\n - \"%d:80\"\n volumes:\n - ./dist:/usr/share/nginx/html:ro\n", port) } return "services:\n" + svc } func dotEnvExample(name, stackType string, port int) string { _ = port // host port is set by compose; container uses its own internal port header := fmt.Sprintf("# %s environment\n# Copy to .env and fill in values\n\n", name) switch stackType { case "node": return header + "PORT=3000\n# DATABASE_URL=\n# SECRET_KEY=\n" case "python": return header + "PORT=8000\n# DATABASE_URL=\n# SECRET_KEY=\n" case "go": return header + "PORT=8080\n# DATABASE_URL=\n" } return header } func caddySnippet(subdomain, proxyHost string, port int) string { return fmt.Sprintf("%s {\n\treverse_proxy %s:%d\n}\n", subdomain, proxyHost, port) } // --- local starter files --- func nodePackageJSON(name string) string { return fmt.Sprintf(`{ "name": "%s", "version": "0.1.0", "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "start": "node dist/index.js" }, "dependencies": {}, "devDependencies": { "typescript": "^5.0.0", "ts-node": "^10.0.0", "@types/node": "^20.0.0" } } `, name) } func tsconfigJSON() string { return `{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "outDir": "dist", "rootDir": "src", "strict": true, "esModuleInterop": true } } ` } func nodeIndexTS() string { // Template literal backtick handled via concatenation return "import http from 'http';\n\n" + "const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;\n\n" + "const server = http.createServer((_req, res) => {\n" + " res.writeHead(200, { 'Content-Type': 'text/plain' });\n" + " res.end('Hello World\\n');\n" + "});\n\n" + "server.listen(port, () => {\n" + " console.log(`Server running on port ${port}`);\n" + "});\n" } func nodeDockerfile() string { return `FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:20-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules EXPOSE 3000 CMD ["node", "dist/index.js"] ` } func pythonApp() string { return `from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "Hello World" if __name__ == "__main__": app.run(host="0.0.0.0", port=8000) ` } func pythonDockerfile() string { return `FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "app.py"] ` } func goMod(name string) string { return fmt.Sprintf("module %s\n\ngo 1.22\n", name) } func goMain() string { return `package main import ( "fmt" "net/http" "os" ) func main() { port := os.Getenv("PORT") if port == "" { port = "8080" } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello World") }) fmt.Println("Listening on :" + port) if err := http.ListenAndServe(":"+port, nil); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } ` } func goDockerfile() string { return `FROM golang:1.22-alpine AS builder WORKDIR /app COPY go.* ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -o app . FROM scratch COPY --from=builder /app/app /app EXPOSE 8080 CMD ["/app"] ` } func staticHTML(name string) string { return fmt.Sprintf(`