@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.
Files changed (172) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-lint.log +498 -0
  3. package/.turbo/turbo-test.log +21 -0
  4. package/.turbo/turbo-typecheck.log +4 -0
  5. package/dist/__tests__/compatibility-scorer.test.d.ts +2 -0
  6. package/dist/__tests__/compatibility-scorer.test.d.ts.map +1 -0
  7. package/dist/__tests__/compatibility-scorer.test.js +226 -0
  8. package/dist/__tests__/compatibility-scorer.test.js.map +1 -0
  9. package/dist/__tests__/rules-engine.test.d.ts +2 -0
  10. package/dist/__tests__/rules-engine.test.d.ts.map +1 -0
  11. package/dist/__tests__/rules-engine.test.js +161 -0
  12. package/dist/__tests__/rules-engine.test.js.map +1 -0
  13. package/dist/__tests__/scaffold-orchestrator.test.d.ts +2 -0
  14. package/dist/__tests__/scaffold-orchestrator.test.d.ts.map +1 -0
  15. package/dist/__tests__/scaffold-orchestrator.test.js +149 -0
  16. package/dist/__tests__/scaffold-orchestrator.test.js.map +1 -0
  17. package/dist/__tests__/stack-engine.test.d.ts +2 -0
  18. package/dist/__tests__/stack-engine.test.d.ts.map +1 -0
  19. package/dist/__tests__/stack-engine.test.js +278 -0
  20. package/dist/__tests__/stack-engine.test.js.map +1 -0
  21. package/dist/db/database.d.ts +9 -0
  22. package/dist/db/database.d.ts.map +1 -0
  23. package/dist/db/database.js +106 -0
  24. package/dist/db/database.js.map +1 -0
  25. package/dist/db/index.d.ts +2 -0
  26. package/dist/db/index.d.ts.map +1 -0
  27. package/dist/db/index.js +2 -0
  28. package/dist/db/index.js.map +1 -0
  29. package/dist/engine/compatibility-scorer.d.ts +37 -0
  30. package/dist/engine/compatibility-scorer.d.ts.map +1 -0
  31. package/dist/engine/compatibility-scorer.js +178 -0
  32. package/dist/engine/compatibility-scorer.js.map +1 -0
  33. package/dist/engine/compose-generator.d.ts +35 -0
  34. package/dist/engine/compose-generator.d.ts.map +1 -0
  35. package/dist/engine/compose-generator.js +95 -0
  36. package/dist/engine/compose-generator.js.map +1 -0
  37. package/dist/engine/cost-estimator.d.ts +22 -0
  38. package/dist/engine/cost-estimator.d.ts.map +1 -0
  39. package/dist/engine/cost-estimator.js +451 -0
  40. package/dist/engine/cost-estimator.js.map +1 -0
  41. package/dist/engine/env-analyzer.d.ts +36 -0
  42. package/dist/engine/env-analyzer.d.ts.map +1 -0
  43. package/dist/engine/env-analyzer.js +111 -0
  44. package/dist/engine/env-analyzer.js.map +1 -0
  45. package/dist/engine/health-checker.d.ts +20 -0
  46. package/dist/engine/health-checker.d.ts.map +1 -0
  47. package/dist/engine/health-checker.js +377 -0
  48. package/dist/engine/health-checker.js.map +1 -0
  49. package/dist/engine/index.d.ts +11 -0
  50. package/dist/engine/index.d.ts.map +1 -0
  51. package/dist/engine/index.js +7 -0
  52. package/dist/engine/index.js.map +1 -0
  53. package/dist/engine/infra-generator.d.ts +26 -0
  54. package/dist/engine/infra-generator.d.ts.map +1 -0
  55. package/dist/engine/infra-generator.js +751 -0
  56. package/dist/engine/infra-generator.js.map +1 -0
  57. package/dist/engine/migration-planner.d.ts +34 -0
  58. package/dist/engine/migration-planner.d.ts.map +1 -0
  59. package/dist/engine/migration-planner.js +427 -0
  60. package/dist/engine/migration-planner.js.map +1 -0
  61. package/dist/engine/performance-profiler.d.ts +22 -0
  62. package/dist/engine/performance-profiler.d.ts.map +1 -0
  63. package/dist/engine/performance-profiler.js +292 -0
  64. package/dist/engine/performance-profiler.js.map +1 -0
  65. package/dist/engine/plugin-loader.d.ts +36 -0
  66. package/dist/engine/plugin-loader.d.ts.map +1 -0
  67. package/dist/engine/plugin-loader.js +157 -0
  68. package/dist/engine/plugin-loader.js.map +1 -0
  69. package/dist/engine/preferences.d.ts +24 -0
  70. package/dist/engine/preferences.d.ts.map +1 -0
  71. package/dist/engine/preferences.js +62 -0
  72. package/dist/engine/preferences.js.map +1 -0
  73. package/dist/engine/rules-engine.d.ts +31 -0
  74. package/dist/engine/rules-engine.d.ts.map +1 -0
  75. package/dist/engine/rules-engine.js +179 -0
  76. package/dist/engine/rules-engine.js.map +1 -0
  77. package/dist/engine/runtime-manager.d.ts +65 -0
  78. package/dist/engine/runtime-manager.d.ts.map +1 -0
  79. package/dist/engine/runtime-manager.js +181 -0
  80. package/dist/engine/runtime-manager.js.map +1 -0
  81. package/dist/engine/scaffold-orchestrator.d.ts +103 -0
  82. package/dist/engine/scaffold-orchestrator.d.ts.map +1 -0
  83. package/dist/engine/scaffold-orchestrator.js +934 -0
  84. package/dist/engine/scaffold-orchestrator.js.map +1 -0
  85. package/dist/engine/stack-detector.d.ts +21 -0
  86. package/dist/engine/stack-detector.d.ts.map +1 -0
  87. package/dist/engine/stack-detector.js +313 -0
  88. package/dist/engine/stack-detector.js.map +1 -0
  89. package/dist/engine/stack-differ.d.ts +26 -0
  90. package/dist/engine/stack-differ.d.ts.map +1 -0
  91. package/dist/engine/stack-differ.js +80 -0
  92. package/dist/engine/stack-differ.js.map +1 -0
  93. package/dist/engine/stack-engine.d.ts +54 -0
  94. package/dist/engine/stack-engine.d.ts.map +1 -0
  95. package/dist/engine/stack-engine.js +186 -0
  96. package/dist/engine/stack-engine.js.map +1 -0
  97. package/dist/engine/stack-serializer.d.ts +32 -0
  98. package/dist/engine/stack-serializer.d.ts.map +1 -0
  99. package/dist/engine/stack-serializer.js +75 -0
  100. package/dist/engine/stack-serializer.js.map +1 -0
  101. package/dist/engine/standards-linter.d.ts +34 -0
  102. package/dist/engine/standards-linter.d.ts.map +1 -0
  103. package/dist/engine/standards-linter.js +162 -0
  104. package/dist/engine/standards-linter.js.map +1 -0
  105. package/dist/engine/tech-installer.d.ts +37 -0
  106. package/dist/engine/tech-installer.d.ts.map +1 -0
  107. package/dist/engine/tech-installer.js +508 -0
  108. package/dist/engine/tech-installer.js.map +1 -0
  109. package/dist/index.d.ts +39 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +25 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/types/index.d.ts +6 -0
  114. package/dist/types/index.d.ts.map +1 -0
  115. package/dist/types/index.js +2 -0
  116. package/dist/types/index.js.map +1 -0
  117. package/dist/types/project.d.ts +33 -0
  118. package/dist/types/project.d.ts.map +1 -0
  119. package/dist/types/project.js +6 -0
  120. package/dist/types/project.js.map +1 -0
  121. package/dist/types/stack.d.ts +29 -0
  122. package/dist/types/stack.d.ts.map +1 -0
  123. package/dist/types/stack.js +6 -0
  124. package/dist/types/stack.js.map +1 -0
  125. package/dist/types/technology.d.ts +47 -0
  126. package/dist/types/technology.d.ts.map +1 -0
  127. package/dist/types/technology.js +6 -0
  128. package/dist/types/technology.js.map +1 -0
  129. package/dist/types/template.d.ts +34 -0
  130. package/dist/types/template.d.ts.map +1 -0
  131. package/dist/types/template.js +6 -0
  132. package/dist/types/template.js.map +1 -0
  133. package/dist/types/validation.d.ts +20 -0
  134. package/dist/types/validation.d.ts.map +1 -0
  135. package/dist/types/validation.js +5 -0
  136. package/dist/types/validation.js.map +1 -0
  137. package/package.json +39 -0
  138. package/src/__tests__/compatibility-scorer.test.ts +264 -0
  139. package/src/__tests__/rules-engine.test.ts +170 -0
  140. package/src/__tests__/scaffold-orchestrator.test.ts +161 -0
  141. package/src/__tests__/stack-engine.test.ts +328 -0
  142. package/src/db/database.ts +112 -0
  143. package/src/db/index.ts +1 -0
  144. package/src/engine/compatibility-scorer.ts +222 -0
  145. package/src/engine/compose-generator.ts +134 -0
  146. package/src/engine/cost-estimator.ts +498 -0
  147. package/src/engine/env-analyzer.ts +156 -0
  148. package/src/engine/health-checker.ts +421 -0
  149. package/src/engine/index.ts +17 -0
  150. package/src/engine/infra-generator.ts +837 -0
  151. package/src/engine/migration-planner.ts +496 -0
  152. package/src/engine/performance-profiler.ts +354 -0
  153. package/src/engine/plugin-loader.ts +216 -0
  154. package/src/engine/preferences.ts +85 -0
  155. package/src/engine/rules-engine.ts +204 -0
  156. package/src/engine/runtime-manager.ts +207 -0
  157. package/src/engine/scaffold-orchestrator.ts +1052 -0
  158. package/src/engine/stack-detector.ts +345 -0
  159. package/src/engine/stack-differ.ts +118 -0
  160. package/src/engine/stack-engine.ts +258 -0
  161. package/src/engine/stack-serializer.ts +95 -0
  162. package/src/engine/standards-linter.ts +210 -0
  163. package/src/engine/tech-installer.ts +650 -0
  164. package/src/index.ts +78 -0
  165. package/src/types/index.ts +10 -0
  166. package/src/types/project.ts +36 -0
  167. package/src/types/stack.ts +32 -0
  168. package/src/types/technology.ts +58 -0
  169. package/src/types/template.ts +37 -0
  170. package/src/types/validation.ts +22 -0
  171. package/tsconfig.json +10 -0
  172. 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
+ }