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