@stackweld/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-lint.log +498 -0
- package/.turbo/turbo-test.log +21 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/dist/__tests__/compatibility-scorer.test.d.ts +2 -0
- package/dist/__tests__/compatibility-scorer.test.d.ts.map +1 -0
- package/dist/__tests__/compatibility-scorer.test.js +226 -0
- package/dist/__tests__/compatibility-scorer.test.js.map +1 -0
- package/dist/__tests__/rules-engine.test.d.ts +2 -0
- package/dist/__tests__/rules-engine.test.d.ts.map +1 -0
- package/dist/__tests__/rules-engine.test.js +161 -0
- package/dist/__tests__/rules-engine.test.js.map +1 -0
- package/dist/__tests__/scaffold-orchestrator.test.d.ts +2 -0
- package/dist/__tests__/scaffold-orchestrator.test.d.ts.map +1 -0
- package/dist/__tests__/scaffold-orchestrator.test.js +149 -0
- package/dist/__tests__/scaffold-orchestrator.test.js.map +1 -0
- package/dist/__tests__/stack-engine.test.d.ts +2 -0
- package/dist/__tests__/stack-engine.test.d.ts.map +1 -0
- package/dist/__tests__/stack-engine.test.js +278 -0
- package/dist/__tests__/stack-engine.test.js.map +1 -0
- package/dist/db/database.d.ts +9 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +106 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +2 -0
- package/dist/db/index.js.map +1 -0
- package/dist/engine/compatibility-scorer.d.ts +37 -0
- package/dist/engine/compatibility-scorer.d.ts.map +1 -0
- package/dist/engine/compatibility-scorer.js +178 -0
- package/dist/engine/compatibility-scorer.js.map +1 -0
- package/dist/engine/compose-generator.d.ts +35 -0
- package/dist/engine/compose-generator.d.ts.map +1 -0
- package/dist/engine/compose-generator.js +95 -0
- package/dist/engine/compose-generator.js.map +1 -0
- package/dist/engine/cost-estimator.d.ts +22 -0
- package/dist/engine/cost-estimator.d.ts.map +1 -0
- package/dist/engine/cost-estimator.js +451 -0
- package/dist/engine/cost-estimator.js.map +1 -0
- package/dist/engine/env-analyzer.d.ts +36 -0
- package/dist/engine/env-analyzer.d.ts.map +1 -0
- package/dist/engine/env-analyzer.js +111 -0
- package/dist/engine/env-analyzer.js.map +1 -0
- package/dist/engine/health-checker.d.ts +20 -0
- package/dist/engine/health-checker.d.ts.map +1 -0
- package/dist/engine/health-checker.js +377 -0
- package/dist/engine/health-checker.js.map +1 -0
- package/dist/engine/index.d.ts +11 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +7 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/infra-generator.d.ts +26 -0
- package/dist/engine/infra-generator.d.ts.map +1 -0
- package/dist/engine/infra-generator.js +751 -0
- package/dist/engine/infra-generator.js.map +1 -0
- package/dist/engine/migration-planner.d.ts +34 -0
- package/dist/engine/migration-planner.d.ts.map +1 -0
- package/dist/engine/migration-planner.js +427 -0
- package/dist/engine/migration-planner.js.map +1 -0
- package/dist/engine/performance-profiler.d.ts +22 -0
- package/dist/engine/performance-profiler.d.ts.map +1 -0
- package/dist/engine/performance-profiler.js +292 -0
- package/dist/engine/performance-profiler.js.map +1 -0
- package/dist/engine/plugin-loader.d.ts +36 -0
- package/dist/engine/plugin-loader.d.ts.map +1 -0
- package/dist/engine/plugin-loader.js +157 -0
- package/dist/engine/plugin-loader.js.map +1 -0
- package/dist/engine/preferences.d.ts +24 -0
- package/dist/engine/preferences.d.ts.map +1 -0
- package/dist/engine/preferences.js +62 -0
- package/dist/engine/preferences.js.map +1 -0
- package/dist/engine/rules-engine.d.ts +31 -0
- package/dist/engine/rules-engine.d.ts.map +1 -0
- package/dist/engine/rules-engine.js +179 -0
- package/dist/engine/rules-engine.js.map +1 -0
- package/dist/engine/runtime-manager.d.ts +65 -0
- package/dist/engine/runtime-manager.d.ts.map +1 -0
- package/dist/engine/runtime-manager.js +181 -0
- package/dist/engine/runtime-manager.js.map +1 -0
- package/dist/engine/scaffold-orchestrator.d.ts +103 -0
- package/dist/engine/scaffold-orchestrator.d.ts.map +1 -0
- package/dist/engine/scaffold-orchestrator.js +934 -0
- package/dist/engine/scaffold-orchestrator.js.map +1 -0
- package/dist/engine/stack-detector.d.ts +21 -0
- package/dist/engine/stack-detector.d.ts.map +1 -0
- package/dist/engine/stack-detector.js +313 -0
- package/dist/engine/stack-detector.js.map +1 -0
- package/dist/engine/stack-differ.d.ts +26 -0
- package/dist/engine/stack-differ.d.ts.map +1 -0
- package/dist/engine/stack-differ.js +80 -0
- package/dist/engine/stack-differ.js.map +1 -0
- package/dist/engine/stack-engine.d.ts +54 -0
- package/dist/engine/stack-engine.d.ts.map +1 -0
- package/dist/engine/stack-engine.js +186 -0
- package/dist/engine/stack-engine.js.map +1 -0
- package/dist/engine/stack-serializer.d.ts +32 -0
- package/dist/engine/stack-serializer.d.ts.map +1 -0
- package/dist/engine/stack-serializer.js +75 -0
- package/dist/engine/stack-serializer.js.map +1 -0
- package/dist/engine/standards-linter.d.ts +34 -0
- package/dist/engine/standards-linter.d.ts.map +1 -0
- package/dist/engine/standards-linter.js +162 -0
- package/dist/engine/standards-linter.js.map +1 -0
- package/dist/engine/tech-installer.d.ts +37 -0
- package/dist/engine/tech-installer.d.ts.map +1 -0
- package/dist/engine/tech-installer.js +508 -0
- package/dist/engine/tech-installer.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +33 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +6 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/stack.d.ts +29 -0
- package/dist/types/stack.d.ts.map +1 -0
- package/dist/types/stack.js +6 -0
- package/dist/types/stack.js.map +1 -0
- package/dist/types/technology.d.ts +47 -0
- package/dist/types/technology.d.ts.map +1 -0
- package/dist/types/technology.js +6 -0
- package/dist/types/technology.js.map +1 -0
- package/dist/types/template.d.ts +34 -0
- package/dist/types/template.d.ts.map +1 -0
- package/dist/types/template.js +6 -0
- package/dist/types/template.js.map +1 -0
- package/dist/types/validation.d.ts +20 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +5 -0
- package/dist/types/validation.js.map +1 -0
- package/package.json +39 -0
- package/src/__tests__/compatibility-scorer.test.ts +264 -0
- package/src/__tests__/rules-engine.test.ts +170 -0
- package/src/__tests__/scaffold-orchestrator.test.ts +161 -0
- package/src/__tests__/stack-engine.test.ts +328 -0
- package/src/db/database.ts +112 -0
- package/src/db/index.ts +1 -0
- package/src/engine/compatibility-scorer.ts +222 -0
- package/src/engine/compose-generator.ts +134 -0
- package/src/engine/cost-estimator.ts +498 -0
- package/src/engine/env-analyzer.ts +156 -0
- package/src/engine/health-checker.ts +421 -0
- package/src/engine/index.ts +17 -0
- package/src/engine/infra-generator.ts +837 -0
- package/src/engine/migration-planner.ts +496 -0
- package/src/engine/performance-profiler.ts +354 -0
- package/src/engine/plugin-loader.ts +216 -0
- package/src/engine/preferences.ts +85 -0
- package/src/engine/rules-engine.ts +204 -0
- package/src/engine/runtime-manager.ts +207 -0
- package/src/engine/scaffold-orchestrator.ts +1052 -0
- package/src/engine/stack-detector.ts +345 -0
- package/src/engine/stack-differ.ts +118 -0
- package/src/engine/stack-engine.ts +258 -0
- package/src/engine/stack-serializer.ts +95 -0
- package/src/engine/standards-linter.ts +210 -0
- package/src/engine/tech-installer.ts +650 -0
- package/src/index.ts +78 -0
- package/src/types/index.ts +10 -0
- package/src/types/project.ts +36 -0
- package/src/types/stack.ts +32 -0
- package/src/types/technology.ts +58 -0
- package/src/types/template.ts +37 -0
- package/src/types/validation.ts +22 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure as Code Generator — Produces deployment files for VPS, AWS, and GCP targets.
|
|
3
|
+
* No disk I/O — returns structured data for writing.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ─── Types ────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export type DeployTarget = "vps" | "aws" | "gcp";
|
|
9
|
+
|
|
10
|
+
export interface InfraOutput {
|
|
11
|
+
target: DeployTarget;
|
|
12
|
+
files: Array<{ path: string; content: string }>;
|
|
13
|
+
instructions: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface InfraTechnology {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
category: string;
|
|
20
|
+
dockerImage?: string;
|
|
21
|
+
defaultPort?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type RuntimeKind = "node" | "python" | "go" | "rust";
|
|
25
|
+
|
|
26
|
+
// ─── Runtime Detection ────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const RUNTIME_HINTS: Record<string, RuntimeKind> = {
|
|
29
|
+
nodejs: "node",
|
|
30
|
+
node: "node",
|
|
31
|
+
nextjs: "node",
|
|
32
|
+
react: "node",
|
|
33
|
+
vue: "node",
|
|
34
|
+
angular: "node",
|
|
35
|
+
express: "node",
|
|
36
|
+
fastify: "node",
|
|
37
|
+
nestjs: "node",
|
|
38
|
+
nuxt: "node",
|
|
39
|
+
svelte: "node",
|
|
40
|
+
sveltekit: "node",
|
|
41
|
+
remix: "node",
|
|
42
|
+
astro: "node",
|
|
43
|
+
typescript: "node",
|
|
44
|
+
bun: "node",
|
|
45
|
+
python: "python",
|
|
46
|
+
fastapi: "python",
|
|
47
|
+
django: "python",
|
|
48
|
+
flask: "python",
|
|
49
|
+
go: "go",
|
|
50
|
+
gin: "go",
|
|
51
|
+
echo: "go",
|
|
52
|
+
fiber: "go",
|
|
53
|
+
rust: "rust",
|
|
54
|
+
actix: "rust",
|
|
55
|
+
axum: "rust",
|
|
56
|
+
rocket: "rust",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function detectRuntime(technologies: InfraTechnology[]): RuntimeKind {
|
|
60
|
+
for (const tech of technologies) {
|
|
61
|
+
const hint = RUNTIME_HINTS[tech.id.toLowerCase()];
|
|
62
|
+
if (hint) return hint;
|
|
63
|
+
}
|
|
64
|
+
return "node";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function detectAppPort(technologies: InfraTechnology[]): number {
|
|
68
|
+
for (const tech of technologies) {
|
|
69
|
+
if (tech.category === "frontend" || tech.category === "backend") {
|
|
70
|
+
if (tech.defaultPort) return tech.defaultPort;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return 3000;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sanitizeName(name: string): string {
|
|
77
|
+
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Dockerfile Generators ────────────────────────────
|
|
81
|
+
|
|
82
|
+
function generateDockerfile(runtime: RuntimeKind, appPort: number): string {
|
|
83
|
+
switch (runtime) {
|
|
84
|
+
case "node":
|
|
85
|
+
return `# Multi-stage build for Node.js
|
|
86
|
+
FROM node:20-alpine AS builder
|
|
87
|
+
|
|
88
|
+
WORKDIR /app
|
|
89
|
+
|
|
90
|
+
COPY package*.json pnpm-lock.yaml* yarn.lock* ./
|
|
91
|
+
RUN corepack enable 2>/dev/null; \\
|
|
92
|
+
if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; \\
|
|
93
|
+
elif [ -f yarn.lock ]; then yarn install --frozen-lockfile; \\
|
|
94
|
+
else npm ci; fi
|
|
95
|
+
|
|
96
|
+
COPY . .
|
|
97
|
+
RUN if [ -f pnpm-lock.yaml ]; then pnpm build; \\
|
|
98
|
+
elif [ -f yarn.lock ]; then yarn build; \\
|
|
99
|
+
else npm run build; fi
|
|
100
|
+
|
|
101
|
+
FROM node:20-alpine AS runner
|
|
102
|
+
|
|
103
|
+
WORKDIR /app
|
|
104
|
+
ENV NODE_ENV=production
|
|
105
|
+
RUN addgroup --system --gid 1001 app && adduser --system --uid 1001 app
|
|
106
|
+
|
|
107
|
+
COPY --from=builder /app/package*.json ./
|
|
108
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
109
|
+
COPY --from=builder /app/dist ./dist
|
|
110
|
+
COPY --from=builder /app/.next ./.next 2>/dev/null || true
|
|
111
|
+
COPY --from=builder /app/public ./public 2>/dev/null || true
|
|
112
|
+
|
|
113
|
+
USER app
|
|
114
|
+
EXPOSE ${appPort}
|
|
115
|
+
|
|
116
|
+
CMD ["node", "dist/index.js"]
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
case "python":
|
|
120
|
+
return `# Multi-stage build for Python
|
|
121
|
+
FROM python:3.12-slim AS builder
|
|
122
|
+
|
|
123
|
+
WORKDIR /app
|
|
124
|
+
|
|
125
|
+
COPY requirements*.txt pyproject.toml* ./
|
|
126
|
+
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt 2>/dev/null || \\
|
|
127
|
+
pip install --no-cache-dir --prefix=/install . 2>/dev/null || true
|
|
128
|
+
|
|
129
|
+
COPY . .
|
|
130
|
+
|
|
131
|
+
FROM python:3.12-slim AS runner
|
|
132
|
+
|
|
133
|
+
WORKDIR /app
|
|
134
|
+
ENV PYTHONDONTWRITEBYTECODE=1
|
|
135
|
+
ENV PYTHONUNBUFFERED=1
|
|
136
|
+
|
|
137
|
+
RUN addgroup --system --gid 1001 app && adduser --system --uid 1001 app
|
|
138
|
+
|
|
139
|
+
COPY --from=builder /install /usr/local
|
|
140
|
+
COPY --from=builder /app .
|
|
141
|
+
|
|
142
|
+
USER app
|
|
143
|
+
EXPOSE ${appPort}
|
|
144
|
+
|
|
145
|
+
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "${appPort}"]
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
case "go":
|
|
149
|
+
return `# Multi-stage build for Go
|
|
150
|
+
FROM golang:1.22-alpine AS builder
|
|
151
|
+
|
|
152
|
+
WORKDIR /app
|
|
153
|
+
|
|
154
|
+
COPY go.mod go.sum ./
|
|
155
|
+
RUN go mod download
|
|
156
|
+
|
|
157
|
+
COPY . .
|
|
158
|
+
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .
|
|
159
|
+
|
|
160
|
+
FROM alpine:3.19 AS runner
|
|
161
|
+
|
|
162
|
+
RUN apk --no-cache add ca-certificates
|
|
163
|
+
RUN addgroup -S app && adduser -S app -G app
|
|
164
|
+
|
|
165
|
+
WORKDIR /app
|
|
166
|
+
COPY --from=builder /app/server .
|
|
167
|
+
|
|
168
|
+
USER app
|
|
169
|
+
EXPOSE ${appPort}
|
|
170
|
+
|
|
171
|
+
CMD ["./server"]
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
case "rust":
|
|
175
|
+
return `# Multi-stage build for Rust
|
|
176
|
+
FROM rust:1.77-slim AS builder
|
|
177
|
+
|
|
178
|
+
WORKDIR /app
|
|
179
|
+
|
|
180
|
+
COPY Cargo.toml Cargo.lock ./
|
|
181
|
+
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src
|
|
182
|
+
|
|
183
|
+
COPY . .
|
|
184
|
+
RUN touch src/main.rs && cargo build --release
|
|
185
|
+
|
|
186
|
+
FROM debian:bookworm-slim AS runner
|
|
187
|
+
|
|
188
|
+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
|
|
189
|
+
RUN addgroup --system app && adduser --system --ingroup app app
|
|
190
|
+
|
|
191
|
+
WORKDIR /app
|
|
192
|
+
COPY --from=builder /app/target/release/app .
|
|
193
|
+
|
|
194
|
+
USER app
|
|
195
|
+
EXPOSE ${appPort}
|
|
196
|
+
|
|
197
|
+
CMD ["./app"]
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─── VPS Target ───────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
function generateVpsFiles(
|
|
205
|
+
technologies: InfraTechnology[],
|
|
206
|
+
projectName: string,
|
|
207
|
+
runtime: RuntimeKind,
|
|
208
|
+
appPort: number,
|
|
209
|
+
): Array<{ path: string; content: string }> {
|
|
210
|
+
const slug = sanitizeName(projectName);
|
|
211
|
+
const files: Array<{ path: string; content: string }> = [];
|
|
212
|
+
|
|
213
|
+
// Dockerfile
|
|
214
|
+
files.push({
|
|
215
|
+
path: "Dockerfile",
|
|
216
|
+
content: generateDockerfile(runtime, appPort),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// docker-compose.prod.yml
|
|
220
|
+
const composeServices: string[] = [];
|
|
221
|
+
|
|
222
|
+
composeServices.push(` app:
|
|
223
|
+
build: .
|
|
224
|
+
container_name: ${slug}-app
|
|
225
|
+
restart: unless-stopped
|
|
226
|
+
ports:
|
|
227
|
+
- "\${APP_PORT:-${appPort}}:${appPort}"
|
|
228
|
+
env_file:
|
|
229
|
+
- .env.production
|
|
230
|
+
deploy:
|
|
231
|
+
resources:
|
|
232
|
+
limits:
|
|
233
|
+
memory: 512M
|
|
234
|
+
cpus: "1.0"
|
|
235
|
+
logging:
|
|
236
|
+
driver: json-file
|
|
237
|
+
options:
|
|
238
|
+
max-size: "10m"
|
|
239
|
+
max-file: "3"
|
|
240
|
+
networks:
|
|
241
|
+
- ${slug}-net`);
|
|
242
|
+
|
|
243
|
+
// Add database services
|
|
244
|
+
for (const tech of technologies) {
|
|
245
|
+
if (tech.dockerImage && tech.category === "database") {
|
|
246
|
+
const svcName = tech.id.replace(/[^a-z0-9]/g, "");
|
|
247
|
+
const port = tech.defaultPort || 5432;
|
|
248
|
+
composeServices.push(` ${svcName}:
|
|
249
|
+
image: ${tech.dockerImage}
|
|
250
|
+
container_name: ${slug}-${svcName}
|
|
251
|
+
restart: unless-stopped
|
|
252
|
+
ports:
|
|
253
|
+
- "\${${tech.id.toUpperCase()}_PORT:-${port}}:${port}"
|
|
254
|
+
volumes:
|
|
255
|
+
- ${svcName}-data:/var/lib/${svcName === "postgresql" ? "postgresql/data" : svcName}
|
|
256
|
+
deploy:
|
|
257
|
+
resources:
|
|
258
|
+
limits:
|
|
259
|
+
memory: 256M
|
|
260
|
+
cpus: "0.5"
|
|
261
|
+
logging:
|
|
262
|
+
driver: json-file
|
|
263
|
+
options:
|
|
264
|
+
max-size: "10m"
|
|
265
|
+
max-file: "3"
|
|
266
|
+
networks:
|
|
267
|
+
- ${slug}-net`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const volumeEntries = technologies
|
|
272
|
+
.filter((t) => t.dockerImage && t.category === "database")
|
|
273
|
+
.map((t) => ` ${t.id.replace(/[^a-z0-9]/g, "")}-data:`)
|
|
274
|
+
.join("\n");
|
|
275
|
+
|
|
276
|
+
const composeContent = `version: "3.9"
|
|
277
|
+
|
|
278
|
+
services:
|
|
279
|
+
${composeServices.join("\n\n")}
|
|
280
|
+
|
|
281
|
+
${volumeEntries ? `volumes:\n${volumeEntries}\n` : ""}
|
|
282
|
+
networks:
|
|
283
|
+
${slug}-net:
|
|
284
|
+
driver: bridge
|
|
285
|
+
`;
|
|
286
|
+
|
|
287
|
+
files.push({
|
|
288
|
+
path: "docker-compose.prod.yml",
|
|
289
|
+
content: composeContent,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// nginx.conf
|
|
293
|
+
files.push({
|
|
294
|
+
path: "nginx.conf",
|
|
295
|
+
content: `upstream app_backend {
|
|
296
|
+
server 127.0.0.1:${appPort};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
server {
|
|
300
|
+
listen 80;
|
|
301
|
+
server_name \${DOMAIN};
|
|
302
|
+
|
|
303
|
+
location / {
|
|
304
|
+
proxy_pass http://app_backend;
|
|
305
|
+
proxy_http_version 1.1;
|
|
306
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
307
|
+
proxy_set_header Connection "upgrade";
|
|
308
|
+
proxy_set_header Host $host;
|
|
309
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
310
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
311
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
312
|
+
proxy_cache_bypass $http_upgrade;
|
|
313
|
+
proxy_read_timeout 86400;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
# Health check endpoint
|
|
317
|
+
location /health {
|
|
318
|
+
proxy_pass http://app_backend/health;
|
|
319
|
+
access_log off;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# Static assets caching
|
|
323
|
+
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
324
|
+
proxy_pass http://app_backend;
|
|
325
|
+
expires 30d;
|
|
326
|
+
add_header Cache-Control "public, immutable";
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
`,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// deploy.sh
|
|
333
|
+
files.push({
|
|
334
|
+
path: "deploy.sh",
|
|
335
|
+
content: `#!/usr/bin/env bash
|
|
336
|
+
set -euo pipefail
|
|
337
|
+
|
|
338
|
+
# ── ${projectName} — VPS Deploy Script ──
|
|
339
|
+
|
|
340
|
+
REMOTE_USER="\${DEPLOY_USER:-deploy}"
|
|
341
|
+
REMOTE_HOST="\${DEPLOY_HOST:?Set DEPLOY_HOST}"
|
|
342
|
+
REMOTE_DIR="\${DEPLOY_DIR:-/opt/${slug}}"
|
|
343
|
+
|
|
344
|
+
echo "=> Syncing files to $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR"
|
|
345
|
+
rsync -avz --exclude node_modules --exclude .git --exclude .env \\
|
|
346
|
+
. "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR"
|
|
347
|
+
|
|
348
|
+
echo "=> Building and starting containers"
|
|
349
|
+
ssh "$REMOTE_USER@$REMOTE_HOST" << 'EOF'
|
|
350
|
+
cd \${REMOTE_DIR}
|
|
351
|
+
docker compose -f docker-compose.prod.yml pull
|
|
352
|
+
docker compose -f docker-compose.prod.yml build --no-cache
|
|
353
|
+
docker compose -f docker-compose.prod.yml up -d
|
|
354
|
+
docker compose -f docker-compose.prod.yml ps
|
|
355
|
+
EOF
|
|
356
|
+
|
|
357
|
+
echo "=> Deploy complete"
|
|
358
|
+
`,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// .env.production.example
|
|
362
|
+
const envLines = [
|
|
363
|
+
`# ${projectName} — Production Environment`,
|
|
364
|
+
`# Copy to .env.production and fill in real values`,
|
|
365
|
+
"",
|
|
366
|
+
`APP_PORT=${appPort}`,
|
|
367
|
+
"NODE_ENV=production",
|
|
368
|
+
];
|
|
369
|
+
|
|
370
|
+
for (const tech of technologies) {
|
|
371
|
+
if (tech.category === "database") {
|
|
372
|
+
const upper = tech.id.toUpperCase().replace(/-/g, "_");
|
|
373
|
+
envLines.push("");
|
|
374
|
+
envLines.push(`# ${tech.name}`);
|
|
375
|
+
envLines.push(`${upper}_HOST=localhost`);
|
|
376
|
+
envLines.push(`${upper}_PORT=${tech.defaultPort || 5432}`);
|
|
377
|
+
envLines.push(`${upper}_USER=app`);
|
|
378
|
+
envLines.push(`${upper}_PASSWORD=changeme`);
|
|
379
|
+
envLines.push(`${upper}_DB=${slug}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
files.push({
|
|
384
|
+
path: ".env.production.example",
|
|
385
|
+
content: `${envLines.join("\n")}\n`,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return files;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ─── AWS Target ───────────────────────────────────────
|
|
392
|
+
|
|
393
|
+
function generateAwsFiles(
|
|
394
|
+
_technologies: InfraTechnology[],
|
|
395
|
+
projectName: string,
|
|
396
|
+
runtime: RuntimeKind,
|
|
397
|
+
appPort: number,
|
|
398
|
+
): Array<{ path: string; content: string }> {
|
|
399
|
+
const slug = sanitizeName(projectName);
|
|
400
|
+
const files: Array<{ path: string; content: string }> = [];
|
|
401
|
+
|
|
402
|
+
// Dockerfile (same base)
|
|
403
|
+
files.push({
|
|
404
|
+
path: "Dockerfile",
|
|
405
|
+
content: generateDockerfile(runtime, appPort),
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// ECS Task Definition
|
|
409
|
+
files.push({
|
|
410
|
+
path: "aws/task-definition.json",
|
|
411
|
+
content: `${JSON.stringify(
|
|
412
|
+
{
|
|
413
|
+
family: slug,
|
|
414
|
+
networkMode: "awsvpc",
|
|
415
|
+
requiresCompatibilities: ["FARGATE"],
|
|
416
|
+
cpu: "256",
|
|
417
|
+
memory: "512",
|
|
418
|
+
executionRoleArn: `arn:aws:iam::\${AWS_ACCOUNT_ID}:role/${slug}-execution-role`,
|
|
419
|
+
taskRoleArn: `arn:aws:iam::\${AWS_ACCOUNT_ID}:role/${slug}-task-role`,
|
|
420
|
+
containerDefinitions: [
|
|
421
|
+
{
|
|
422
|
+
name: slug,
|
|
423
|
+
image: `\${AWS_ACCOUNT_ID}.dkr.ecr.\${AWS_REGION}.amazonaws.com/${slug}:latest`,
|
|
424
|
+
portMappings: [
|
|
425
|
+
{
|
|
426
|
+
containerPort: appPort,
|
|
427
|
+
protocol: "tcp",
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
essential: true,
|
|
431
|
+
logConfiguration: {
|
|
432
|
+
logDriver: "awslogs",
|
|
433
|
+
options: {
|
|
434
|
+
"awslogs-group": `/ecs/${slug}`,
|
|
435
|
+
"awslogs-region": `\${AWS_REGION}`,
|
|
436
|
+
"awslogs-stream-prefix": "ecs",
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
healthCheck: {
|
|
440
|
+
command: ["CMD-SHELL", `curl -f http://localhost:${appPort}/health || exit 1`],
|
|
441
|
+
interval: 30,
|
|
442
|
+
timeout: 5,
|
|
443
|
+
retries: 3,
|
|
444
|
+
startPeriod: 60,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
},
|
|
449
|
+
null,
|
|
450
|
+
2,
|
|
451
|
+
)}\n`,
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// CloudFormation template
|
|
455
|
+
files.push({
|
|
456
|
+
path: "aws/cloudformation.yml",
|
|
457
|
+
content: `AWSTemplateFormatVersion: "2010-09-09"
|
|
458
|
+
Description: "${projectName} — ECS Fargate deployment"
|
|
459
|
+
|
|
460
|
+
Parameters:
|
|
461
|
+
VpcId:
|
|
462
|
+
Type: AWS::EC2::VPC::Id
|
|
463
|
+
SubnetIds:
|
|
464
|
+
Type: List<AWS::EC2::Subnet::Id>
|
|
465
|
+
ContainerImage:
|
|
466
|
+
Type: String
|
|
467
|
+
AppPort:
|
|
468
|
+
Type: Number
|
|
469
|
+
Default: ${appPort}
|
|
470
|
+
|
|
471
|
+
Resources:
|
|
472
|
+
Cluster:
|
|
473
|
+
Type: AWS::ECS::Cluster
|
|
474
|
+
Properties:
|
|
475
|
+
ClusterName: ${slug}-cluster
|
|
476
|
+
CapacityProviders:
|
|
477
|
+
- FARGATE
|
|
478
|
+
- FARGATE_SPOT
|
|
479
|
+
|
|
480
|
+
LogGroup:
|
|
481
|
+
Type: AWS::Logs::LogGroup
|
|
482
|
+
Properties:
|
|
483
|
+
LogGroupName: /ecs/${slug}
|
|
484
|
+
RetentionInDays: 30
|
|
485
|
+
|
|
486
|
+
SecurityGroup:
|
|
487
|
+
Type: AWS::EC2::SecurityGroup
|
|
488
|
+
Properties:
|
|
489
|
+
GroupDescription: ${slug} ECS tasks
|
|
490
|
+
VpcId: !Ref VpcId
|
|
491
|
+
SecurityGroupIngress:
|
|
492
|
+
- IpProtocol: tcp
|
|
493
|
+
FromPort: !Ref AppPort
|
|
494
|
+
ToPort: !Ref AppPort
|
|
495
|
+
SourceSecurityGroupId: !Ref ALBSecurityGroup
|
|
496
|
+
|
|
497
|
+
ALBSecurityGroup:
|
|
498
|
+
Type: AWS::EC2::SecurityGroup
|
|
499
|
+
Properties:
|
|
500
|
+
GroupDescription: ${slug} ALB
|
|
501
|
+
VpcId: !Ref VpcId
|
|
502
|
+
SecurityGroupIngress:
|
|
503
|
+
- IpProtocol: tcp
|
|
504
|
+
FromPort: 80
|
|
505
|
+
ToPort: 80
|
|
506
|
+
CidrIp: 0.0.0.0/0
|
|
507
|
+
- IpProtocol: tcp
|
|
508
|
+
FromPort: 443
|
|
509
|
+
ToPort: 443
|
|
510
|
+
CidrIp: 0.0.0.0/0
|
|
511
|
+
|
|
512
|
+
ALB:
|
|
513
|
+
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
|
|
514
|
+
Properties:
|
|
515
|
+
Name: ${slug}-alb
|
|
516
|
+
Scheme: internet-facing
|
|
517
|
+
Subnets: !Ref SubnetIds
|
|
518
|
+
SecurityGroups:
|
|
519
|
+
- !Ref ALBSecurityGroup
|
|
520
|
+
|
|
521
|
+
TargetGroup:
|
|
522
|
+
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
523
|
+
Properties:
|
|
524
|
+
Name: ${slug}-tg
|
|
525
|
+
Port: !Ref AppPort
|
|
526
|
+
Protocol: HTTP
|
|
527
|
+
VpcId: !Ref VpcId
|
|
528
|
+
TargetType: ip
|
|
529
|
+
HealthCheckPath: /health
|
|
530
|
+
HealthCheckIntervalSeconds: 30
|
|
531
|
+
|
|
532
|
+
Listener:
|
|
533
|
+
Type: AWS::ElasticLoadBalancingV2::Listener
|
|
534
|
+
Properties:
|
|
535
|
+
LoadBalancerArn: !Ref ALB
|
|
536
|
+
Port: 80
|
|
537
|
+
Protocol: HTTP
|
|
538
|
+
DefaultActions:
|
|
539
|
+
- Type: forward
|
|
540
|
+
TargetGroupArn: !Ref TargetGroup
|
|
541
|
+
|
|
542
|
+
TaskDefinition:
|
|
543
|
+
Type: AWS::ECS::TaskDefinition
|
|
544
|
+
Properties:
|
|
545
|
+
Family: ${slug}
|
|
546
|
+
Cpu: "256"
|
|
547
|
+
Memory: "512"
|
|
548
|
+
NetworkMode: awsvpc
|
|
549
|
+
RequiresCompatibilities:
|
|
550
|
+
- FARGATE
|
|
551
|
+
ExecutionRoleArn: !Sub "arn:aws:iam::\${AWS::AccountId}:role/${slug}-execution-role"
|
|
552
|
+
ContainerDefinitions:
|
|
553
|
+
- Name: ${slug}
|
|
554
|
+
Image: !Ref ContainerImage
|
|
555
|
+
PortMappings:
|
|
556
|
+
- ContainerPort: !Ref AppPort
|
|
557
|
+
LogConfiguration:
|
|
558
|
+
LogDriver: awslogs
|
|
559
|
+
Options:
|
|
560
|
+
awslogs-group: !Ref LogGroup
|
|
561
|
+
awslogs-region: !Ref AWS::Region
|
|
562
|
+
awslogs-stream-prefix: ecs
|
|
563
|
+
|
|
564
|
+
Service:
|
|
565
|
+
Type: AWS::ECS::Service
|
|
566
|
+
DependsOn: Listener
|
|
567
|
+
Properties:
|
|
568
|
+
ServiceName: ${slug}-service
|
|
569
|
+
Cluster: !Ref Cluster
|
|
570
|
+
TaskDefinition: !Ref TaskDefinition
|
|
571
|
+
DesiredCount: 1
|
|
572
|
+
LaunchType: FARGATE
|
|
573
|
+
NetworkConfiguration:
|
|
574
|
+
AwsvpcConfiguration:
|
|
575
|
+
AssignPublicIp: ENABLED
|
|
576
|
+
Subnets: !Ref SubnetIds
|
|
577
|
+
SecurityGroups:
|
|
578
|
+
- !Ref SecurityGroup
|
|
579
|
+
LoadBalancers:
|
|
580
|
+
- ContainerName: ${slug}
|
|
581
|
+
ContainerPort: !Ref AppPort
|
|
582
|
+
TargetGroupArn: !Ref TargetGroup
|
|
583
|
+
|
|
584
|
+
Outputs:
|
|
585
|
+
ALBDnsName:
|
|
586
|
+
Description: ALB DNS name
|
|
587
|
+
Value: !GetAtt ALB.DNSName
|
|
588
|
+
ClusterArn:
|
|
589
|
+
Description: ECS Cluster ARN
|
|
590
|
+
Value: !GetAtt Cluster.Arn
|
|
591
|
+
`,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Deploy script
|
|
595
|
+
files.push({
|
|
596
|
+
path: "deploy-aws.sh",
|
|
597
|
+
content: `#!/usr/bin/env bash
|
|
598
|
+
set -euo pipefail
|
|
599
|
+
|
|
600
|
+
# ── ${projectName} — AWS ECS Deploy Script ──
|
|
601
|
+
|
|
602
|
+
AWS_REGION="\${AWS_REGION:-us-east-1}"
|
|
603
|
+
AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
|
|
604
|
+
ECR_REPO="\${AWS_ACCOUNT_ID}.dkr.ecr.\${AWS_REGION}.amazonaws.com/${slug}"
|
|
605
|
+
CLUSTER="${slug}-cluster"
|
|
606
|
+
SERVICE="${slug}-service"
|
|
607
|
+
|
|
608
|
+
echo "=> Authenticating with ECR"
|
|
609
|
+
aws ecr get-login-password --region "$AWS_REGION" | \\
|
|
610
|
+
docker login --username AWS --password-stdin "$ECR_REPO"
|
|
611
|
+
|
|
612
|
+
echo "=> Building Docker image"
|
|
613
|
+
docker build -t "${slug}:latest" .
|
|
614
|
+
|
|
615
|
+
echo "=> Tagging and pushing to ECR"
|
|
616
|
+
docker tag "${slug}:latest" "$ECR_REPO:latest"
|
|
617
|
+
docker push "$ECR_REPO:latest"
|
|
618
|
+
|
|
619
|
+
echo "=> Updating ECS service (force new deployment)"
|
|
620
|
+
aws ecs update-service \\
|
|
621
|
+
--cluster "$CLUSTER" \\
|
|
622
|
+
--service "$SERVICE" \\
|
|
623
|
+
--force-new-deployment \\
|
|
624
|
+
--region "$AWS_REGION"
|
|
625
|
+
|
|
626
|
+
echo "=> Waiting for service to stabilize..."
|
|
627
|
+
aws ecs wait services-stable \\
|
|
628
|
+
--cluster "$CLUSTER" \\
|
|
629
|
+
--services "$SERVICE" \\
|
|
630
|
+
--region "$AWS_REGION"
|
|
631
|
+
|
|
632
|
+
echo "=> Deploy complete"
|
|
633
|
+
`,
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
return files;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ─── GCP Target ───────────────────────────────────────
|
|
640
|
+
|
|
641
|
+
function generateGcpFiles(
|
|
642
|
+
_technologies: InfraTechnology[],
|
|
643
|
+
projectName: string,
|
|
644
|
+
runtime: RuntimeKind,
|
|
645
|
+
appPort: number,
|
|
646
|
+
): Array<{ path: string; content: string }> {
|
|
647
|
+
const slug = sanitizeName(projectName);
|
|
648
|
+
const files: Array<{ path: string; content: string }> = [];
|
|
649
|
+
|
|
650
|
+
// Dockerfile (same base)
|
|
651
|
+
files.push({
|
|
652
|
+
path: "Dockerfile",
|
|
653
|
+
content: generateDockerfile(runtime, appPort),
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Cloud Build config
|
|
657
|
+
files.push({
|
|
658
|
+
path: "gcp/cloudbuild.yaml",
|
|
659
|
+
content: `steps:
|
|
660
|
+
- name: "gcr.io/cloud-builders/docker"
|
|
661
|
+
args:
|
|
662
|
+
- "build"
|
|
663
|
+
- "-t"
|
|
664
|
+
- "gcr.io/$PROJECT_ID/${slug}:$SHORT_SHA"
|
|
665
|
+
- "-t"
|
|
666
|
+
- "gcr.io/$PROJECT_ID/${slug}:latest"
|
|
667
|
+
- "."
|
|
668
|
+
|
|
669
|
+
- name: "gcr.io/cloud-builders/docker"
|
|
670
|
+
args:
|
|
671
|
+
- "push"
|
|
672
|
+
- "gcr.io/$PROJECT_ID/${slug}:$SHORT_SHA"
|
|
673
|
+
|
|
674
|
+
- name: "gcr.io/cloud-builders/docker"
|
|
675
|
+
args:
|
|
676
|
+
- "push"
|
|
677
|
+
- "gcr.io/$PROJECT_ID/${slug}:latest"
|
|
678
|
+
|
|
679
|
+
- name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
|
|
680
|
+
entrypoint: "gcloud"
|
|
681
|
+
args:
|
|
682
|
+
- "run"
|
|
683
|
+
- "deploy"
|
|
684
|
+
- "${slug}"
|
|
685
|
+
- "--image"
|
|
686
|
+
- "gcr.io/$PROJECT_ID/${slug}:$SHORT_SHA"
|
|
687
|
+
- "--region"
|
|
688
|
+
- "\${_REGION}"
|
|
689
|
+
- "--platform"
|
|
690
|
+
- "managed"
|
|
691
|
+
|
|
692
|
+
substitutions:
|
|
693
|
+
_REGION: "us-central1"
|
|
694
|
+
|
|
695
|
+
images:
|
|
696
|
+
- "gcr.io/$PROJECT_ID/${slug}:$SHORT_SHA"
|
|
697
|
+
- "gcr.io/$PROJECT_ID/${slug}:latest"
|
|
698
|
+
`,
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Cloud Run service definition
|
|
702
|
+
files.push({
|
|
703
|
+
path: "gcp/service.yaml",
|
|
704
|
+
content: `apiVersion: serving.knative.dev/v1
|
|
705
|
+
kind: Service
|
|
706
|
+
metadata:
|
|
707
|
+
name: ${slug}
|
|
708
|
+
annotations:
|
|
709
|
+
run.googleapis.com/ingress: all
|
|
710
|
+
spec:
|
|
711
|
+
template:
|
|
712
|
+
metadata:
|
|
713
|
+
annotations:
|
|
714
|
+
autoscaling.knative.dev/minScale: "0"
|
|
715
|
+
autoscaling.knative.dev/maxScale: "10"
|
|
716
|
+
run.googleapis.com/cpu-throttling: "true"
|
|
717
|
+
spec:
|
|
718
|
+
containerConcurrency: 80
|
|
719
|
+
timeoutSeconds: 300
|
|
720
|
+
containers:
|
|
721
|
+
- image: gcr.io/PROJECT_ID/${slug}:latest
|
|
722
|
+
ports:
|
|
723
|
+
- containerPort: ${appPort}
|
|
724
|
+
resources:
|
|
725
|
+
limits:
|
|
726
|
+
cpu: "1"
|
|
727
|
+
memory: 512Mi
|
|
728
|
+
startupProbe:
|
|
729
|
+
httpGet:
|
|
730
|
+
path: /health
|
|
731
|
+
port: ${appPort}
|
|
732
|
+
initialDelaySeconds: 5
|
|
733
|
+
periodSeconds: 10
|
|
734
|
+
failureThreshold: 3
|
|
735
|
+
livenessProbe:
|
|
736
|
+
httpGet:
|
|
737
|
+
path: /health
|
|
738
|
+
port: ${appPort}
|
|
739
|
+
periodSeconds: 15
|
|
740
|
+
`,
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Deploy script
|
|
744
|
+
files.push({
|
|
745
|
+
path: "deploy-gcp.sh",
|
|
746
|
+
content: `#!/usr/bin/env bash
|
|
747
|
+
set -euo pipefail
|
|
748
|
+
|
|
749
|
+
# ── ${projectName} — GCP Cloud Run Deploy Script ──
|
|
750
|
+
|
|
751
|
+
GCP_PROJECT="\${GCP_PROJECT:?Set GCP_PROJECT}"
|
|
752
|
+
GCP_REGION="\${GCP_REGION:-us-central1}"
|
|
753
|
+
IMAGE="gcr.io/$GCP_PROJECT/${slug}"
|
|
754
|
+
|
|
755
|
+
echo "=> Building Docker image"
|
|
756
|
+
docker build -t "$IMAGE:latest" .
|
|
757
|
+
|
|
758
|
+
echo "=> Pushing to GCR"
|
|
759
|
+
docker push "$IMAGE:latest"
|
|
760
|
+
|
|
761
|
+
echo "=> Deploying to Cloud Run"
|
|
762
|
+
gcloud run deploy ${slug} \\
|
|
763
|
+
--image "$IMAGE:latest" \\
|
|
764
|
+
--platform managed \\
|
|
765
|
+
--region "$GCP_REGION" \\
|
|
766
|
+
--project "$GCP_PROJECT" \\
|
|
767
|
+
--allow-unauthenticated \\
|
|
768
|
+
--port ${appPort} \\
|
|
769
|
+
--memory 512Mi \\
|
|
770
|
+
--cpu 1 \\
|
|
771
|
+
--min-instances 0 \\
|
|
772
|
+
--max-instances 10
|
|
773
|
+
|
|
774
|
+
echo "=> Deploy complete"
|
|
775
|
+
gcloud run services describe ${slug} \\
|
|
776
|
+
--platform managed \\
|
|
777
|
+
--region "$GCP_REGION" \\
|
|
778
|
+
--project "$GCP_PROJECT" \\
|
|
779
|
+
--format "value(status.url)"
|
|
780
|
+
`,
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
return files;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// ─── Main Function ────────────────────────────────────
|
|
787
|
+
|
|
788
|
+
export function generateInfra(
|
|
789
|
+
technologies: InfraTechnology[],
|
|
790
|
+
projectName: string,
|
|
791
|
+
target: DeployTarget,
|
|
792
|
+
): InfraOutput {
|
|
793
|
+
const runtime = detectRuntime(technologies);
|
|
794
|
+
const appPort = detectAppPort(technologies);
|
|
795
|
+
|
|
796
|
+
let files: Array<{ path: string; content: string }>;
|
|
797
|
+
let instructions: string[];
|
|
798
|
+
|
|
799
|
+
switch (target) {
|
|
800
|
+
case "vps":
|
|
801
|
+
files = generateVpsFiles(technologies, projectName, runtime, appPort);
|
|
802
|
+
instructions = [
|
|
803
|
+
`Set DEPLOY_HOST and DEPLOY_USER in your environment`,
|
|
804
|
+
`Copy .env.production.example to .env.production and fill in real values`,
|
|
805
|
+
`Run: chmod +x deploy.sh`,
|
|
806
|
+
`Run: ./deploy.sh`,
|
|
807
|
+
`Configure nginx on your VPS using the generated nginx.conf`,
|
|
808
|
+
`Set up SSL with: certbot --nginx -d yourdomain.com`,
|
|
809
|
+
];
|
|
810
|
+
break;
|
|
811
|
+
|
|
812
|
+
case "aws":
|
|
813
|
+
files = generateAwsFiles(technologies, projectName, runtime, appPort);
|
|
814
|
+
instructions = [
|
|
815
|
+
`Create an ECR repository: aws ecr create-repository --repository-name ${sanitizeName(projectName)}`,
|
|
816
|
+
`Create IAM roles: ${sanitizeName(projectName)}-execution-role and ${sanitizeName(projectName)}-task-role`,
|
|
817
|
+
`Deploy infrastructure: aws cloudformation deploy --template-file aws/cloudformation.yml --stack-name ${sanitizeName(projectName)} --parameter-overrides VpcId=vpc-xxx SubnetIds=subnet-xxx ContainerImage=xxx`,
|
|
818
|
+
`Run: chmod +x deploy-aws.sh`,
|
|
819
|
+
`Run: ./deploy-aws.sh`,
|
|
820
|
+
];
|
|
821
|
+
break;
|
|
822
|
+
|
|
823
|
+
case "gcp":
|
|
824
|
+
files = generateGcpFiles(technologies, projectName, runtime, appPort);
|
|
825
|
+
instructions = [
|
|
826
|
+
`Set GCP_PROJECT environment variable`,
|
|
827
|
+
`Enable Cloud Run API: gcloud services enable run.googleapis.com`,
|
|
828
|
+
`Enable Container Registry: gcloud services enable containerregistry.googleapis.com`,
|
|
829
|
+
`Run: chmod +x deploy-gcp.sh`,
|
|
830
|
+
`Run: ./deploy-gcp.sh`,
|
|
831
|
+
`(Optional) Set up Cloud Build trigger: gcloud builds triggers create github --repo-name=your-repo --branch-pattern=main --build-config=gcp/cloudbuild.yaml`,
|
|
832
|
+
];
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return { target, files, instructions };
|
|
837
|
+
}
|