thevoidforge 21.0.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 (328) hide show
  1. package/dist/scripts/vault-read.d.ts +11 -0
  2. package/dist/scripts/vault-read.js +89 -0
  3. package/dist/scripts/voidforge.d.ts +20 -0
  4. package/dist/scripts/voidforge.js +404 -0
  5. package/dist/tsconfig.tsbuildinfo +1 -0
  6. package/dist/wizard/api/auth.d.ts +5 -0
  7. package/dist/wizard/api/auth.js +133 -0
  8. package/dist/wizard/api/blueprint.d.ts +45 -0
  9. package/dist/wizard/api/blueprint.js +184 -0
  10. package/dist/wizard/api/cloud-providers.d.ts +16 -0
  11. package/dist/wizard/api/cloud-providers.js +363 -0
  12. package/dist/wizard/api/credentials.d.ts +1 -0
  13. package/dist/wizard/api/credentials.js +258 -0
  14. package/dist/wizard/api/danger-room.d.ts +18 -0
  15. package/dist/wizard/api/danger-room.js +401 -0
  16. package/dist/wizard/api/deploy.d.ts +4 -0
  17. package/dist/wizard/api/deploy.js +164 -0
  18. package/dist/wizard/api/prd.d.ts +1 -0
  19. package/dist/wizard/api/prd.js +363 -0
  20. package/dist/wizard/api/project.d.ts +1 -0
  21. package/dist/wizard/api/project.js +239 -0
  22. package/dist/wizard/api/projects.d.ts +6 -0
  23. package/dist/wizard/api/projects.js +648 -0
  24. package/dist/wizard/api/provision.d.ts +4 -0
  25. package/dist/wizard/api/provision.js +535 -0
  26. package/dist/wizard/api/terminal.d.ts +25 -0
  27. package/dist/wizard/api/terminal.js +241 -0
  28. package/dist/wizard/api/users.d.ts +6 -0
  29. package/dist/wizard/api/users.js +244 -0
  30. package/dist/wizard/api/war-room.d.ts +14 -0
  31. package/dist/wizard/api/war-room.js +45 -0
  32. package/dist/wizard/lib/ad-platform-core.d.ts +6 -0
  33. package/dist/wizard/lib/ad-platform-core.js +1 -0
  34. package/dist/wizard/lib/adapters/index.d.ts +52 -0
  35. package/dist/wizard/lib/adapters/index.js +38 -0
  36. package/dist/wizard/lib/adapters/sandbox-bank.d.ts +17 -0
  37. package/dist/wizard/lib/adapters/sandbox-bank.js +77 -0
  38. package/dist/wizard/lib/adapters/sandbox.d.ts +39 -0
  39. package/dist/wizard/lib/adapters/sandbox.js +174 -0
  40. package/dist/wizard/lib/adapters/stripe.d.ts +19 -0
  41. package/dist/wizard/lib/adapters/stripe.js +143 -0
  42. package/dist/wizard/lib/adapters/types.d.ts +9 -0
  43. package/dist/wizard/lib/adapters/types.js +10 -0
  44. package/dist/wizard/lib/agent-memory.d.ts +36 -0
  45. package/dist/wizard/lib/agent-memory.js +114 -0
  46. package/dist/wizard/lib/anomaly-detection.d.ts +59 -0
  47. package/dist/wizard/lib/anomaly-detection.js +122 -0
  48. package/dist/wizard/lib/anthropic.d.ts +21 -0
  49. package/dist/wizard/lib/anthropic.js +105 -0
  50. package/dist/wizard/lib/asset-scanner.d.ts +23 -0
  51. package/dist/wizard/lib/asset-scanner.js +107 -0
  52. package/dist/wizard/lib/audit-log.d.ts +23 -0
  53. package/dist/wizard/lib/audit-log.js +70 -0
  54. package/dist/wizard/lib/autonomy-controller.d.ts +76 -0
  55. package/dist/wizard/lib/autonomy-controller.js +183 -0
  56. package/dist/wizard/lib/body-parser.d.ts +2 -0
  57. package/dist/wizard/lib/body-parser.js +36 -0
  58. package/dist/wizard/lib/build-analytics.d.ts +39 -0
  59. package/dist/wizard/lib/build-analytics.js +91 -0
  60. package/dist/wizard/lib/build-step.d.ts +21 -0
  61. package/dist/wizard/lib/build-step.js +104 -0
  62. package/dist/wizard/lib/campaign-proposer.d.ts +39 -0
  63. package/dist/wizard/lib/campaign-proposer.js +180 -0
  64. package/dist/wizard/lib/campaign-state-machine.d.ts +63 -0
  65. package/dist/wizard/lib/campaign-state-machine.js +114 -0
  66. package/dist/wizard/lib/ci-generator.d.ts +14 -0
  67. package/dist/wizard/lib/ci-generator.js +187 -0
  68. package/dist/wizard/lib/claude-merge.d.ts +38 -0
  69. package/dist/wizard/lib/claude-merge.js +115 -0
  70. package/dist/wizard/lib/codegen/erd-gen.d.ts +16 -0
  71. package/dist/wizard/lib/codegen/erd-gen.js +98 -0
  72. package/dist/wizard/lib/codegen/integrations.d.ts +18 -0
  73. package/dist/wizard/lib/codegen/integrations.js +189 -0
  74. package/dist/wizard/lib/codegen/openapi-gen.d.ts +15 -0
  75. package/dist/wizard/lib/codegen/openapi-gen.js +79 -0
  76. package/dist/wizard/lib/codegen/prisma-types.d.ts +15 -0
  77. package/dist/wizard/lib/codegen/prisma-types.js +44 -0
  78. package/dist/wizard/lib/codegen/seed-gen.d.ts +16 -0
  79. package/dist/wizard/lib/codegen/seed-gen.js +128 -0
  80. package/dist/wizard/lib/compliance.d.ts +51 -0
  81. package/dist/wizard/lib/compliance.js +112 -0
  82. package/dist/wizard/lib/correlation-engine.d.ts +59 -0
  83. package/dist/wizard/lib/correlation-engine.js +151 -0
  84. package/dist/wizard/lib/cost-estimator.d.ts +22 -0
  85. package/dist/wizard/lib/cost-estimator.js +72 -0
  86. package/dist/wizard/lib/cost-tracker.d.ts +27 -0
  87. package/dist/wizard/lib/cost-tracker.js +37 -0
  88. package/dist/wizard/lib/daemon-aggregator.d.ts +71 -0
  89. package/dist/wizard/lib/daemon-aggregator.js +204 -0
  90. package/dist/wizard/lib/daemon-core.d.ts +6 -0
  91. package/dist/wizard/lib/daemon-core.js +5 -0
  92. package/dist/wizard/lib/dashboard-data.d.ts +132 -0
  93. package/dist/wizard/lib/dashboard-data.js +336 -0
  94. package/dist/wizard/lib/dashboard-ws.d.ts +25 -0
  95. package/dist/wizard/lib/dashboard-ws.js +91 -0
  96. package/dist/wizard/lib/deep-current.d.ts +77 -0
  97. package/dist/wizard/lib/deep-current.js +234 -0
  98. package/dist/wizard/lib/deploy-coordinator.d.ts +40 -0
  99. package/dist/wizard/lib/deploy-coordinator.js +86 -0
  100. package/dist/wizard/lib/deploy-log.d.ts +28 -0
  101. package/dist/wizard/lib/deploy-log.js +52 -0
  102. package/dist/wizard/lib/desktop-notify.d.ts +27 -0
  103. package/dist/wizard/lib/desktop-notify.js +98 -0
  104. package/dist/wizard/lib/dns/cloudflare-dns.d.ts +35 -0
  105. package/dist/wizard/lib/dns/cloudflare-dns.js +216 -0
  106. package/dist/wizard/lib/dns/cloudflare-registrar.d.ts +31 -0
  107. package/dist/wizard/lib/dns/cloudflare-registrar.js +148 -0
  108. package/dist/wizard/lib/dns/types.d.ts +22 -0
  109. package/dist/wizard/lib/dns/types.js +4 -0
  110. package/dist/wizard/lib/document-discovery.d.ts +33 -0
  111. package/dist/wizard/lib/document-discovery.js +145 -0
  112. package/dist/wizard/lib/env-validator.d.ts +14 -0
  113. package/dist/wizard/lib/env-validator.js +205 -0
  114. package/dist/wizard/lib/env-writer.d.ts +13 -0
  115. package/dist/wizard/lib/env-writer.js +26 -0
  116. package/dist/wizard/lib/exec.d.ts +30 -0
  117. package/dist/wizard/lib/exec.js +52 -0
  118. package/dist/wizard/lib/experiment.d.ts +70 -0
  119. package/dist/wizard/lib/experiment.js +169 -0
  120. package/dist/wizard/lib/extensions.d.ts +20 -0
  121. package/dist/wizard/lib/extensions.js +183 -0
  122. package/dist/wizard/lib/financial/adapter-factory.d.ts +47 -0
  123. package/dist/wizard/lib/financial/adapter-factory.js +225 -0
  124. package/dist/wizard/lib/financial/billing/base.d.ts +6 -0
  125. package/dist/wizard/lib/financial/billing/base.js +1 -0
  126. package/dist/wizard/lib/financial/billing/google-billing.d.ts +56 -0
  127. package/dist/wizard/lib/financial/billing/google-billing.js +298 -0
  128. package/dist/wizard/lib/financial/billing/meta-billing.d.ts +54 -0
  129. package/dist/wizard/lib/financial/billing/meta-billing.js +243 -0
  130. package/dist/wizard/lib/financial/billing/tiktok-billing.d.ts +54 -0
  131. package/dist/wizard/lib/financial/billing/tiktok-billing.js +260 -0
  132. package/dist/wizard/lib/financial/campaign/base.d.ts +13 -0
  133. package/dist/wizard/lib/financial/campaign/base.js +1 -0
  134. package/dist/wizard/lib/financial/campaign/google-campaign.d.ts +42 -0
  135. package/dist/wizard/lib/financial/campaign/google-campaign.js +388 -0
  136. package/dist/wizard/lib/financial/campaign/meta-campaign.d.ts +41 -0
  137. package/dist/wizard/lib/financial/campaign/meta-campaign.js +311 -0
  138. package/dist/wizard/lib/financial/campaign/sandbox-campaign.d.ts +45 -0
  139. package/dist/wizard/lib/financial/campaign/sandbox-campaign.js +261 -0
  140. package/dist/wizard/lib/financial/campaign/tiktok-campaign.d.ts +40 -0
  141. package/dist/wizard/lib/financial/campaign/tiktok-campaign.js +350 -0
  142. package/dist/wizard/lib/financial/funding-auto.d.ts +44 -0
  143. package/dist/wizard/lib/financial/funding-auto.js +52 -0
  144. package/dist/wizard/lib/financial/funding-policy.d.ts +60 -0
  145. package/dist/wizard/lib/financial/funding-policy.js +179 -0
  146. package/dist/wizard/lib/financial/platform-planner.d.ts +47 -0
  147. package/dist/wizard/lib/financial/platform-planner.js +134 -0
  148. package/dist/wizard/lib/financial/reconciliation-engine.d.ts +78 -0
  149. package/dist/wizard/lib/financial/reconciliation-engine.js +193 -0
  150. package/dist/wizard/lib/financial/registry.d.ts +22 -0
  151. package/dist/wizard/lib/financial/registry.js +26 -0
  152. package/dist/wizard/lib/financial/reporting.d.ts +96 -0
  153. package/dist/wizard/lib/financial/reporting.js +198 -0
  154. package/dist/wizard/lib/financial/stablecoin/base.d.ts +6 -0
  155. package/dist/wizard/lib/financial/stablecoin/base.js +1 -0
  156. package/dist/wizard/lib/financial/stablecoin/circle.d.ts +54 -0
  157. package/dist/wizard/lib/financial/stablecoin/circle.js +367 -0
  158. package/dist/wizard/lib/financial/stablecoin/mercury.d.ts +24 -0
  159. package/dist/wizard/lib/financial/stablecoin/mercury.js +171 -0
  160. package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.d.ts +47 -0
  161. package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.js +202 -0
  162. package/dist/wizard/lib/financial/treasury-planner.d.ts +52 -0
  163. package/dist/wizard/lib/financial/treasury-planner.js +128 -0
  164. package/dist/wizard/lib/financial-core.d.ts +6 -0
  165. package/dist/wizard/lib/financial-core.js +5 -0
  166. package/dist/wizard/lib/financial-vault.d.ts +34 -0
  167. package/dist/wizard/lib/financial-vault.js +199 -0
  168. package/dist/wizard/lib/frontmatter.d.ts +30 -0
  169. package/dist/wizard/lib/frontmatter.js +96 -0
  170. package/dist/wizard/lib/gap-analysis.d.ts +37 -0
  171. package/dist/wizard/lib/gap-analysis.js +218 -0
  172. package/dist/wizard/lib/github.d.ts +22 -0
  173. package/dist/wizard/lib/github.js +261 -0
  174. package/dist/wizard/lib/headless-deploy.d.ts +14 -0
  175. package/dist/wizard/lib/headless-deploy.js +452 -0
  176. package/dist/wizard/lib/health-monitor.d.ts +15 -0
  177. package/dist/wizard/lib/health-monitor.js +91 -0
  178. package/dist/wizard/lib/health-poller.d.ts +9 -0
  179. package/dist/wizard/lib/health-poller.js +123 -0
  180. package/dist/wizard/lib/heartbeat.d.ts +15 -0
  181. package/dist/wizard/lib/heartbeat.js +827 -0
  182. package/dist/wizard/lib/http-helpers.d.ts +9 -0
  183. package/dist/wizard/lib/http-helpers.js +24 -0
  184. package/dist/wizard/lib/image-gen.d.ts +56 -0
  185. package/dist/wizard/lib/image-gen.js +159 -0
  186. package/dist/wizard/lib/instance-sizing.d.ts +26 -0
  187. package/dist/wizard/lib/instance-sizing.js +51 -0
  188. package/dist/wizard/lib/kongo/analytics.d.ts +29 -0
  189. package/dist/wizard/lib/kongo/analytics.js +179 -0
  190. package/dist/wizard/lib/kongo/campaigns.d.ts +52 -0
  191. package/dist/wizard/lib/kongo/campaigns.js +91 -0
  192. package/dist/wizard/lib/kongo/client.d.ts +58 -0
  193. package/dist/wizard/lib/kongo/client.js +221 -0
  194. package/dist/wizard/lib/kongo/jobs.d.ts +57 -0
  195. package/dist/wizard/lib/kongo/jobs.js +122 -0
  196. package/dist/wizard/lib/kongo/pages.d.ts +60 -0
  197. package/dist/wizard/lib/kongo/pages.js +150 -0
  198. package/dist/wizard/lib/kongo/provisioner.d.ts +64 -0
  199. package/dist/wizard/lib/kongo/provisioner.js +116 -0
  200. package/dist/wizard/lib/kongo/seed.d.ts +49 -0
  201. package/dist/wizard/lib/kongo/seed.js +237 -0
  202. package/dist/wizard/lib/kongo/types.d.ts +323 -0
  203. package/dist/wizard/lib/kongo/types.js +11 -0
  204. package/dist/wizard/lib/kongo/variants.d.ts +57 -0
  205. package/dist/wizard/lib/kongo/variants.js +88 -0
  206. package/dist/wizard/lib/kongo/webhooks.d.ts +41 -0
  207. package/dist/wizard/lib/kongo/webhooks.js +112 -0
  208. package/dist/wizard/lib/marker.d.ts +28 -0
  209. package/dist/wizard/lib/marker.js +79 -0
  210. package/dist/wizard/lib/migrator.d.ts +35 -0
  211. package/dist/wizard/lib/migrator.js +190 -0
  212. package/dist/wizard/lib/natural-language-deploy.d.ts +30 -0
  213. package/dist/wizard/lib/natural-language-deploy.js +186 -0
  214. package/dist/wizard/lib/network.d.ts +22 -0
  215. package/dist/wizard/lib/network.js +72 -0
  216. package/dist/wizard/lib/oauth-core.d.ts +6 -0
  217. package/dist/wizard/lib/oauth-core.js +5 -0
  218. package/dist/wizard/lib/open-browser.d.ts +1 -0
  219. package/dist/wizard/lib/open-browser.js +26 -0
  220. package/dist/wizard/lib/patterns/ad-billing-adapter.d.ts +209 -0
  221. package/dist/wizard/lib/patterns/ad-billing-adapter.js +269 -0
  222. package/dist/wizard/lib/patterns/ad-platform-adapter.d.ts +200 -0
  223. package/dist/wizard/lib/patterns/ad-platform-adapter.js +212 -0
  224. package/dist/wizard/lib/patterns/daemon-process.d.ts +88 -0
  225. package/dist/wizard/lib/patterns/daemon-process.js +271 -0
  226. package/dist/wizard/lib/patterns/financial-transaction.d.ts +161 -0
  227. package/dist/wizard/lib/patterns/financial-transaction.js +132 -0
  228. package/dist/wizard/lib/patterns/funding-plan.d.ts +136 -0
  229. package/dist/wizard/lib/patterns/funding-plan.js +200 -0
  230. package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +94 -0
  231. package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +139 -0
  232. package/dist/wizard/lib/patterns/outbound-rate-limiter.d.ts +67 -0
  233. package/dist/wizard/lib/patterns/outbound-rate-limiter.js +216 -0
  234. package/dist/wizard/lib/patterns/revenue-source-adapter.d.ts +96 -0
  235. package/dist/wizard/lib/patterns/revenue-source-adapter.js +182 -0
  236. package/dist/wizard/lib/patterns/stablecoin-adapter.d.ts +218 -0
  237. package/dist/wizard/lib/patterns/stablecoin-adapter.js +264 -0
  238. package/dist/wizard/lib/prd-validator.d.ts +39 -0
  239. package/dist/wizard/lib/prd-validator.js +137 -0
  240. package/dist/wizard/lib/project-init.d.ts +24 -0
  241. package/dist/wizard/lib/project-init.js +193 -0
  242. package/dist/wizard/lib/project-registry.d.ts +86 -0
  243. package/dist/wizard/lib/project-registry.js +359 -0
  244. package/dist/wizard/lib/provision-manifest.d.ts +44 -0
  245. package/dist/wizard/lib/provision-manifest.js +164 -0
  246. package/dist/wizard/lib/provisioner-registry.d.ts +15 -0
  247. package/dist/wizard/lib/provisioner-registry.js +34 -0
  248. package/dist/wizard/lib/provisioners/aws-vps.d.ts +6 -0
  249. package/dist/wizard/lib/provisioners/aws-vps.js +643 -0
  250. package/dist/wizard/lib/provisioners/cloudflare.d.ts +6 -0
  251. package/dist/wizard/lib/provisioners/cloudflare.js +300 -0
  252. package/dist/wizard/lib/provisioners/docker.d.ts +6 -0
  253. package/dist/wizard/lib/provisioners/docker.js +75 -0
  254. package/dist/wizard/lib/provisioners/http-client.d.ts +20 -0
  255. package/dist/wizard/lib/provisioners/http-client.js +79 -0
  256. package/dist/wizard/lib/provisioners/railway.d.ts +6 -0
  257. package/dist/wizard/lib/provisioners/railway.js +413 -0
  258. package/dist/wizard/lib/provisioners/scripts/caddyfile.d.ts +10 -0
  259. package/dist/wizard/lib/provisioners/scripts/caddyfile.js +54 -0
  260. package/dist/wizard/lib/provisioners/scripts/deploy-vps.d.ts +10 -0
  261. package/dist/wizard/lib/provisioners/scripts/deploy-vps.js +112 -0
  262. package/dist/wizard/lib/provisioners/scripts/docker-compose.d.ts +11 -0
  263. package/dist/wizard/lib/provisioners/scripts/docker-compose.js +91 -0
  264. package/dist/wizard/lib/provisioners/scripts/dockerfile.d.ts +5 -0
  265. package/dist/wizard/lib/provisioners/scripts/dockerfile.js +185 -0
  266. package/dist/wizard/lib/provisioners/scripts/ecosystem-config.d.ts +10 -0
  267. package/dist/wizard/lib/provisioners/scripts/ecosystem-config.js +36 -0
  268. package/dist/wizard/lib/provisioners/scripts/provision-vps.d.ts +14 -0
  269. package/dist/wizard/lib/provisioners/scripts/provision-vps.js +202 -0
  270. package/dist/wizard/lib/provisioners/scripts/rollback-vps.d.ts +10 -0
  271. package/dist/wizard/lib/provisioners/scripts/rollback-vps.js +67 -0
  272. package/dist/wizard/lib/provisioners/self-deploy.d.ts +41 -0
  273. package/dist/wizard/lib/provisioners/self-deploy.js +185 -0
  274. package/dist/wizard/lib/provisioners/static-s3.d.ts +6 -0
  275. package/dist/wizard/lib/provisioners/static-s3.js +235 -0
  276. package/dist/wizard/lib/provisioners/types.d.ts +40 -0
  277. package/dist/wizard/lib/provisioners/types.js +4 -0
  278. package/dist/wizard/lib/provisioners/vercel.d.ts +6 -0
  279. package/dist/wizard/lib/provisioners/vercel.js +287 -0
  280. package/dist/wizard/lib/pty-manager.d.ts +42 -0
  281. package/dist/wizard/lib/pty-manager.js +231 -0
  282. package/dist/wizard/lib/rate-limiter-core.d.ts +5 -0
  283. package/dist/wizard/lib/rate-limiter-core.js +5 -0
  284. package/dist/wizard/lib/reconciliation.d.ts +43 -0
  285. package/dist/wizard/lib/reconciliation.js +173 -0
  286. package/dist/wizard/lib/revenue-types.d.ts +5 -0
  287. package/dist/wizard/lib/revenue-types.js +1 -0
  288. package/dist/wizard/lib/route-optimizer.d.ts +28 -0
  289. package/dist/wizard/lib/route-optimizer.js +93 -0
  290. package/dist/wizard/lib/s3-deploy.d.ts +19 -0
  291. package/dist/wizard/lib/s3-deploy.js +156 -0
  292. package/dist/wizard/lib/safety-tiers.d.ts +76 -0
  293. package/dist/wizard/lib/safety-tiers.js +134 -0
  294. package/dist/wizard/lib/sentry-generator.d.ts +15 -0
  295. package/dist/wizard/lib/sentry-generator.js +116 -0
  296. package/dist/wizard/lib/server-config.d.ts +13 -0
  297. package/dist/wizard/lib/server-config.js +23 -0
  298. package/dist/wizard/lib/service-install.d.ts +18 -0
  299. package/dist/wizard/lib/service-install.js +182 -0
  300. package/dist/wizard/lib/site-scanner.d.ts +80 -0
  301. package/dist/wizard/lib/site-scanner.js +262 -0
  302. package/dist/wizard/lib/ssh-deploy.d.ts +25 -0
  303. package/dist/wizard/lib/ssh-deploy.js +225 -0
  304. package/dist/wizard/lib/templates.d.ts +24 -0
  305. package/dist/wizard/lib/templates.js +219 -0
  306. package/dist/wizard/lib/totp.d.ts +35 -0
  307. package/dist/wizard/lib/totp.js +276 -0
  308. package/dist/wizard/lib/tower-auth.d.ts +43 -0
  309. package/dist/wizard/lib/tower-auth.js +352 -0
  310. package/dist/wizard/lib/tower-rate-limit.d.ts +14 -0
  311. package/dist/wizard/lib/tower-rate-limit.js +61 -0
  312. package/dist/wizard/lib/tower-session.d.ts +28 -0
  313. package/dist/wizard/lib/tower-session.js +119 -0
  314. package/dist/wizard/lib/treasury-backup.d.ts +23 -0
  315. package/dist/wizard/lib/treasury-backup.js +126 -0
  316. package/dist/wizard/lib/treasury-heartbeat.d.ts +82 -0
  317. package/dist/wizard/lib/treasury-heartbeat.js +1104 -0
  318. package/dist/wizard/lib/updater.d.ts +29 -0
  319. package/dist/wizard/lib/updater.js +190 -0
  320. package/dist/wizard/lib/user-manager.d.ts +39 -0
  321. package/dist/wizard/lib/user-manager.js +182 -0
  322. package/dist/wizard/lib/vault.d.ts +26 -0
  323. package/dist/wizard/lib/vault.js +161 -0
  324. package/dist/wizard/router.d.ts +5 -0
  325. package/dist/wizard/router.js +15 -0
  326. package/dist/wizard/server.d.ts +18 -0
  327. package/dist/wizard/server.js +436 -0
  328. package/package.json +59 -0
@@ -0,0 +1,643 @@
1
+ /**
2
+ * AWS VPS provisioner — EC2 + SG + optional RDS/ElastiCache.
3
+ * Uses @aws-sdk for all AWS API calls.
4
+ */
5
+ import { writeFile, mkdir, chmod } from 'node:fs/promises';
6
+ import { chmodSync } from 'node:fs';
7
+ import { randomBytes } from 'node:crypto';
8
+ import { join } from 'node:path';
9
+ import { recordResourcePending, recordResourceCreated } from '../provision-manifest.js';
10
+ import { rdsInstanceClass, cacheNodeType, isValidInstanceType } from '../instance-sizing.js';
11
+ import { generateProvisionScript } from './scripts/provision-vps.js';
12
+ import { generateDeployScript } from './scripts/deploy-vps.js';
13
+ import { generateRollbackScript } from './scripts/rollback-vps.js';
14
+ import { generateEcosystemConfig } from './scripts/ecosystem-config.js';
15
+ import { generateCaddyfile } from './scripts/caddyfile.js';
16
+ import { appendEnvSection } from '../env-writer.js';
17
+ import { slugify } from './http-client.js';
18
+ const POLL_INTERVAL_MS = 5000;
19
+ const MAX_POLL_MS = 300000; // 5 minutes
20
+ function sleep(ms) {
21
+ return new Promise((r) => setTimeout(r, ms));
22
+ }
23
+ function cancellableSleep(ms, signal) {
24
+ return new Promise((resolve, reject) => {
25
+ if (signal?.aborted) {
26
+ reject(new Error('Aborted'));
27
+ return;
28
+ }
29
+ const timer = setTimeout(resolve, ms);
30
+ signal?.addEventListener('abort', () => { clearTimeout(timer); reject(new Error('Aborted')); }, { once: true });
31
+ });
32
+ }
33
+ export const awsVpsProvisioner = {
34
+ async validate(ctx) {
35
+ const errors = [];
36
+ if (!ctx.projectDir)
37
+ errors.push('Project directory is required');
38
+ if (!ctx.projectName)
39
+ errors.push('Project name is required');
40
+ if (!ctx.credentials['aws-access-key-id'])
41
+ errors.push('AWS Access Key ID is required');
42
+ if (!ctx.credentials['aws-secret-access-key'])
43
+ errors.push('AWS Secret Access Key is required');
44
+ if (ctx.instanceType && !isValidInstanceType(ctx.instanceType)) {
45
+ errors.push(`Invalid instance type: "${ctx.instanceType}". Must be one of: t3.micro, t3.small, t3.medium, t3.large`);
46
+ }
47
+ return errors;
48
+ },
49
+ async provision(ctx, emit) {
50
+ const resources = [];
51
+ const outputs = {};
52
+ const files = [];
53
+ const region = ctx.credentials['aws-region'] || 'us-east-1';
54
+ const slug = slugify(ctx.projectName);
55
+ // Dynamic import of AWS SDK
56
+ let EC2Client;
57
+ let STSClient;
58
+ let GetCallerIdentityCommand;
59
+ let ec2Commands;
60
+ try {
61
+ const ec2Mod = await import('@aws-sdk/client-ec2');
62
+ const stsMod = await import('@aws-sdk/client-sts');
63
+ EC2Client = ec2Mod.EC2Client;
64
+ STSClient = stsMod.STSClient;
65
+ GetCallerIdentityCommand = stsMod.GetCallerIdentityCommand;
66
+ ec2Commands = ec2Mod;
67
+ }
68
+ catch {
69
+ return {
70
+ success: false, resources, outputs, files,
71
+ error: 'AWS SDK not installed. Run: npm install @aws-sdk/client-ec2 @aws-sdk/client-sts @aws-sdk/client-rds @aws-sdk/client-elasticache',
72
+ };
73
+ }
74
+ const awsConfig = {
75
+ region,
76
+ credentials: {
77
+ accessKeyId: ctx.credentials['aws-access-key-id'],
78
+ secretAccessKey: ctx.credentials['aws-secret-access-key'],
79
+ },
80
+ };
81
+ const ec2 = new EC2Client(awsConfig);
82
+ const sts = new STSClient(awsConfig);
83
+ // Step 1: Validate credentials via STS
84
+ emit({ step: 'validate-creds', status: 'started', message: 'Validating AWS credentials' });
85
+ try {
86
+ const identity = await sts.send(new GetCallerIdentityCommand({}));
87
+ const entityName = (identity.Arn ?? 'unknown').split(/[/:]/).pop() ?? 'unknown';
88
+ emit({ step: 'validate-creds', status: 'done', message: `Authenticated as ${entityName}` });
89
+ }
90
+ catch (err) {
91
+ console.error('AWS credential validation error:', err.message);
92
+ emit({ step: 'validate-creds', status: 'error', message: 'Invalid AWS credentials', detail: 'Check AWS Console for details' });
93
+ return { success: false, resources, outputs, files, error: 'AWS credential validation failed' };
94
+ }
95
+ // Step 2: Create key pair
96
+ emit({ step: 'key-pair', status: 'started', message: 'Creating SSH key pair' });
97
+ const keyName = `${slug}-deploy`;
98
+ try {
99
+ await recordResourcePending(ctx.runId, 'key-pair', keyName, region);
100
+ const keyResult = await ec2.send(new ec2Commands.CreateKeyPairCommand({
101
+ KeyName: keyName,
102
+ KeyType: 'ed25519',
103
+ }));
104
+ if (!keyResult.KeyMaterial) {
105
+ throw new Error('AWS returned no key material — key pair may already exist');
106
+ }
107
+ const sshDir = join(ctx.projectDir, '.ssh');
108
+ await mkdir(sshDir, { recursive: true });
109
+ const keyPath = join(sshDir, 'deploy-key.pem');
110
+ await writeFile(keyPath, keyResult.KeyMaterial, 'utf-8');
111
+ await chmod(keyPath, 0o600);
112
+ files.push('.ssh/deploy-key.pem');
113
+ resources.push({ type: 'key-pair', id: keyName, region });
114
+ await recordResourceCreated(ctx.runId, 'key-pair', keyName, region);
115
+ outputs['SSH_KEY_PATH'] = '.ssh/deploy-key.pem';
116
+ emit({ step: 'key-pair', status: 'done', message: `Key pair "${keyName}" created` });
117
+ }
118
+ catch (err) {
119
+ console.error('Key pair creation error:', err.message);
120
+ emit({ step: 'key-pair', status: 'error', message: 'Failed to create key pair', detail: 'Check AWS Console for details' });
121
+ return { success: false, resources, outputs, files, error: 'Failed to create key pair' };
122
+ }
123
+ // Step 3: Create security group
124
+ emit({ step: 'security-group', status: 'started', message: 'Creating security group' });
125
+ let sgId;
126
+ try {
127
+ await recordResourcePending(ctx.runId, 'security-group', `${slug}-sg`, region);
128
+ const sgResult = await ec2.send(new ec2Commands.CreateSecurityGroupCommand({
129
+ GroupName: `${slug}-sg`,
130
+ Description: `VoidForge security group for ${ctx.projectName}`,
131
+ }));
132
+ sgId = sgResult.GroupId ?? '';
133
+ resources.push({ type: 'security-group', id: sgId, region });
134
+ await recordResourceCreated(ctx.runId, 'security-group', sgId, region);
135
+ // Authorize inbound: SSH (22), HTTP (80), HTTPS (443)
136
+ // SSH initially open to 0.0.0.0/0 for provisioning — restricted to deployer IP at end (DEVOPS-R2-001).
137
+ const ingressRules = [
138
+ { IpProtocol: 'tcp', FromPort: 22, ToPort: 22, IpRanges: [{ CidrIp: '0.0.0.0/0', Description: 'SSH' }] },
139
+ { IpProtocol: 'tcp', FromPort: 80, ToPort: 80, IpRanges: [{ CidrIp: '0.0.0.0/0', Description: 'HTTP' }] },
140
+ { IpProtocol: 'tcp', FromPort: 443, ToPort: 443, IpRanges: [{ CidrIp: '0.0.0.0/0', Description: 'HTTPS' }] },
141
+ ];
142
+ // Allow DB port within the SG (self-referencing) so EC2 can reach RDS
143
+ // Uses UserIdGroupPairs to restrict access to instances in the same SG only
144
+ if (ctx.database === 'postgres') {
145
+ ingressRules.push({ IpProtocol: 'tcp', FromPort: 5432, ToPort: 5432, UserIdGroupPairs: [{ GroupId: sgId, Description: 'PostgreSQL (SG-only)' }] });
146
+ }
147
+ else if (ctx.database === 'mysql') {
148
+ ingressRules.push({ IpProtocol: 'tcp', FromPort: 3306, ToPort: 3306, UserIdGroupPairs: [{ GroupId: sgId, Description: 'MySQL (SG-only)' }] });
149
+ }
150
+ // Allow Redis port if cache requested
151
+ if (ctx.cache === 'redis') {
152
+ ingressRules.push({ IpProtocol: 'tcp', FromPort: 6379, ToPort: 6379, UserIdGroupPairs: [{ GroupId: sgId, Description: 'Redis (SG-only)' }] });
153
+ }
154
+ await ec2.send(new ec2Commands.AuthorizeSecurityGroupIngressCommand({
155
+ GroupId: sgId,
156
+ IpPermissions: ingressRules,
157
+ }));
158
+ const portList = ingressRules.map((r) => r.FromPort).join(', ');
159
+ emit({ step: 'security-group', status: 'done', message: `Security group "${slug}-sg" created (ports ${portList})` });
160
+ }
161
+ catch (err) {
162
+ console.error('Security group creation error:', err.message);
163
+ emit({ step: 'security-group', status: 'error', message: 'Failed to create security group', detail: 'Check AWS Console for details' });
164
+ return { success: false, resources, outputs, files, error: 'Failed to create security group' };
165
+ }
166
+ // Step 4: Find latest Amazon Linux 2023 AMI
167
+ emit({ step: 'ami-lookup', status: 'started', message: 'Finding latest Amazon Linux 2023 AMI' });
168
+ let amiId;
169
+ try {
170
+ const amiResult = await ec2.send(new ec2Commands.DescribeImagesCommand({
171
+ Owners: ['amazon'],
172
+ Filters: [
173
+ { Name: 'name', Values: ['al2023-ami-*-x86_64'] },
174
+ { Name: 'state', Values: ['available'] },
175
+ { Name: 'architecture', Values: ['x86_64'] },
176
+ ],
177
+ }));
178
+ const images = (amiResult.Images ?? [])
179
+ .filter((img) => img.ImageId && img.CreationDate)
180
+ .sort((a, b) => (b.CreationDate ?? '').localeCompare(a.CreationDate ?? ''));
181
+ if (images.length === 0) {
182
+ throw new Error('No Amazon Linux 2023 AMI found in this region');
183
+ }
184
+ amiId = images[0].ImageId;
185
+ emit({ step: 'ami-lookup', status: 'done', message: `AMI: ${amiId}` });
186
+ }
187
+ catch (err) {
188
+ console.error('AMI lookup error:', err.message);
189
+ emit({ step: 'ami-lookup', status: 'error', message: 'AMI lookup failed', detail: 'Check AWS Console for details' });
190
+ return { success: false, resources, outputs, files, error: 'AMI lookup failed' };
191
+ }
192
+ // Step 5: Launch EC2 instance
193
+ const ec2InstanceType = (ctx.instanceType || 't3.micro');
194
+ emit({ step: 'launch-ec2', status: 'started', message: `Launching EC2 instance (${ec2InstanceType})` });
195
+ let instanceId;
196
+ try {
197
+ const userDataScript = `#!/bin/bash
198
+ dnf update -y
199
+ dnf install -y git curl`;
200
+ await recordResourcePending(ctx.runId, 'ec2-instance', 'pending', region);
201
+ const runResult = await ec2.send(new ec2Commands.RunInstancesCommand({
202
+ ImageId: amiId,
203
+ InstanceType: ec2InstanceType,
204
+ MinCount: 1,
205
+ MaxCount: 1,
206
+ KeyName: keyName,
207
+ SecurityGroupIds: [sgId],
208
+ UserData: Buffer.from(userDataScript).toString('base64'),
209
+ TagSpecifications: [{
210
+ ResourceType: 'instance',
211
+ Tags: [
212
+ { Key: 'Name', Value: ctx.projectName },
213
+ { Key: 'ManagedBy', Value: 'VoidForge' },
214
+ ],
215
+ }],
216
+ }));
217
+ instanceId = runResult.Instances?.[0]?.InstanceId ?? '';
218
+ if (!instanceId)
219
+ throw new Error('No instance ID returned');
220
+ resources.push({ type: 'ec2-instance', id: instanceId, region });
221
+ await recordResourceCreated(ctx.runId, 'ec2-instance', instanceId, region);
222
+ emit({ step: 'launch-ec2', status: 'done', message: `Instance ${instanceId} launched` });
223
+ }
224
+ catch (err) {
225
+ console.error('EC2 launch error:', err.message);
226
+ emit({ step: 'launch-ec2', status: 'error', message: 'Failed to launch EC2', detail: 'Check AWS Console for details' });
227
+ return { success: false, resources, outputs, files, error: 'Failed to launch EC2 instance' };
228
+ }
229
+ // Step 6: Wait for instance to be running
230
+ emit({ step: 'wait-running', status: 'started', message: 'Waiting for instance to start...' });
231
+ let publicIp = '';
232
+ try {
233
+ const start = Date.now();
234
+ while (Date.now() - start < MAX_POLL_MS) {
235
+ await cancellableSleep(POLL_INTERVAL_MS + Math.random() * 1000, ctx.abortSignal);
236
+ const desc = await ec2.send(new ec2Commands.DescribeInstancesCommand({
237
+ InstanceIds: [instanceId],
238
+ }));
239
+ const instance = desc.Reservations?.[0]?.Instances?.[0];
240
+ const state = instance?.State?.Name;
241
+ if (state === 'running') {
242
+ publicIp = instance?.PublicIpAddress ?? '';
243
+ if (publicIp)
244
+ break;
245
+ }
246
+ if (state === 'terminated' || state === 'shutting-down') {
247
+ throw new Error(`Instance entered state: ${state}`);
248
+ }
249
+ }
250
+ if (!publicIp)
251
+ throw new Error('Instance did not get a public IP within timeout');
252
+ outputs['SSH_HOST'] = publicIp;
253
+ outputs['SSH_USER'] = 'ec2-user';
254
+ emit({ step: 'wait-running', status: 'done', message: `Instance running at ${publicIp}` });
255
+ }
256
+ catch (err) {
257
+ if (err.message === 'Aborted') {
258
+ emit({ step: 'wait-running', status: 'skipped', message: 'EC2 polling cancelled' });
259
+ }
260
+ else {
261
+ console.error('EC2 wait error:', err.message);
262
+ emit({ step: 'wait-running', status: 'error', message: 'Instance failed to start', detail: 'Check AWS Console for details' });
263
+ return { success: false, resources, outputs, files, error: 'EC2 instance failed to start' };
264
+ }
265
+ }
266
+ // Step 7: Optional RDS
267
+ if (ctx.database === 'postgres' || ctx.database === 'mysql') {
268
+ emit({ step: 'rds', status: 'started', message: `Creating RDS instance (${ctx.database})` });
269
+ try {
270
+ const { RDSClient, CreateDBInstanceCommand, DescribeDBInstancesCommand } = await import('@aws-sdk/client-rds');
271
+ const rds = new RDSClient(awsConfig);
272
+ const engine = ctx.database === 'postgres' ? 'postgres' : 'mysql';
273
+ const port = ctx.database === 'postgres' ? 5432 : 3306;
274
+ const dbInstanceId = `${slug}-db`;
275
+ // IG-R2: Random username instead of hardcoded 'admin'
276
+ const dbUsername = `vf_${randomBytes(4).toString('hex')}`;
277
+ const specials = '!@#$%^&*';
278
+ const suffix = String.fromCharCode(65 + Math.floor(Math.random() * 26)) + Math.floor(Math.random() * 10) + specials[Math.floor(Math.random() * specials.length)];
279
+ const dbPassword = randomBytes(16).toString('hex') + suffix;
280
+ await recordResourcePending(ctx.runId, 'rds-instance', dbInstanceId, region);
281
+ await rds.send(new CreateDBInstanceCommand({
282
+ DBInstanceIdentifier: dbInstanceId,
283
+ DBInstanceClass: rdsInstanceClass(ec2InstanceType),
284
+ Engine: engine,
285
+ MasterUsername: dbUsername,
286
+ MasterUserPassword: dbPassword,
287
+ AllocatedStorage: 20,
288
+ PubliclyAccessible: false,
289
+ VpcSecurityGroupIds: [sgId],
290
+ Tags: [
291
+ { Key: 'Name', Value: `${ctx.projectName}-db` },
292
+ { Key: 'ManagedBy', Value: 'VoidForge' },
293
+ ],
294
+ }));
295
+ resources.push({ type: 'rds-instance', id: dbInstanceId, region });
296
+ await recordResourceCreated(ctx.runId, 'rds-instance', dbInstanceId, region);
297
+ outputs['DB_ENGINE'] = engine;
298
+ outputs['DB_PORT'] = String(port);
299
+ outputs['DB_INSTANCE_ID'] = dbInstanceId;
300
+ outputs['DB_USERNAME'] = dbUsername;
301
+ outputs['DB_PASSWORD'] = dbPassword;
302
+ emit({ step: 'rds', status: 'done', message: `RDS instance "${dbInstanceId}" created — waiting for endpoint` });
303
+ // Step 7b: Poll RDS until available (non-fatal on timeout)
304
+ const RDS_POLL_MS = 10000;
305
+ const RDS_TIMEOUT_MS = 900000; // 15 minutes
306
+ const RDS_PROGRESS_MS = 30000;
307
+ emit({ step: 'rds-wait', status: 'started', message: 'Waiting for RDS to become available (5-10 minutes)...' });
308
+ try {
309
+ const rdsStart = Date.now();
310
+ let lastProgress = rdsStart;
311
+ let dbHost = '';
312
+ while (Date.now() - rdsStart < RDS_TIMEOUT_MS) {
313
+ await cancellableSleep(RDS_POLL_MS + Math.random() * 2000, ctx.abortSignal);
314
+ const desc = await rds.send(new DescribeDBInstancesCommand({
315
+ DBInstanceIdentifier: dbInstanceId,
316
+ }));
317
+ const instance = desc.DBInstances?.[0];
318
+ const status = instance?.DBInstanceStatus;
319
+ if (status === 'available') {
320
+ dbHost = instance?.Endpoint?.Address ?? '';
321
+ break;
322
+ }
323
+ // Check for terminal failure states
324
+ const rdsTerminalStates = ['failed', 'deleting', 'deleted', 'incompatible-parameters', 'incompatible-restore', 'storage-full'];
325
+ if (status && rdsTerminalStates.includes(status)) {
326
+ emit({ step: 'rds-wait', status: 'error', message: `RDS entered terminal state: ${status}`, detail: 'Check AWS Console for details' });
327
+ break;
328
+ }
329
+ // Emit progress every 30 seconds
330
+ if (Date.now() - lastProgress >= RDS_PROGRESS_MS) {
331
+ const elapsed = Math.round((Date.now() - rdsStart) / 1000);
332
+ emit({ step: 'rds-wait', status: 'started', message: `RDS status: ${status || 'creating'}... (${elapsed}s elapsed)` });
333
+ lastProgress = Date.now();
334
+ }
335
+ }
336
+ if (dbHost) {
337
+ outputs['DB_HOST'] = dbHost;
338
+ emit({ step: 'rds-wait', status: 'done', message: `RDS available at ${dbHost}` });
339
+ }
340
+ else {
341
+ emit({ step: 'rds-wait', status: 'error', message: 'RDS polling timed out after 15 minutes', detail: `Instance "${dbInstanceId}" is still provisioning. Check the AWS Console for the endpoint and add DB_HOST to your .env manually.` });
342
+ }
343
+ }
344
+ catch (pollErr) {
345
+ if (pollErr.message === 'Aborted') {
346
+ emit({ step: 'rds-wait', status: 'skipped', message: 'RDS polling cancelled' });
347
+ }
348
+ else {
349
+ console.error('RDS polling error:', pollErr.message);
350
+ emit({ step: 'rds-wait', status: 'error', message: 'RDS polling failed', detail: 'Check AWS Console for details' });
351
+ }
352
+ // Non-fatal — continue without DB_HOST
353
+ }
354
+ }
355
+ catch (err) {
356
+ console.error('RDS creation error:', err.message);
357
+ emit({ step: 'rds', status: 'error', message: 'Failed to create RDS instance', detail: 'Check AWS Console for details' });
358
+ // Non-fatal — continue without DB
359
+ }
360
+ }
361
+ else {
362
+ emit({ step: 'rds', status: 'skipped', message: 'No database requested' });
363
+ }
364
+ // Step 8: Optional ElastiCache
365
+ if (ctx.cache === 'redis') {
366
+ emit({ step: 'elasticache', status: 'started', message: 'Creating ElastiCache Redis cluster' });
367
+ try {
368
+ const { ElastiCacheClient, CreateCacheClusterCommand, DescribeCacheClustersCommand } = await import('@aws-sdk/client-elasticache');
369
+ const elasticache = new ElastiCacheClient(awsConfig);
370
+ const clusterId = `${slug}-redis`;
371
+ await recordResourcePending(ctx.runId, 'elasticache-cluster', clusterId, region);
372
+ // Note: CreateCacheClusterCommand does not support AuthToken — Redis AUTH requires
373
+ // CreateReplicationGroupCommand with TransitEncryptionEnabled. Security relies on
374
+ // SG isolation (only instances in the same SG can reach the Redis port). (IG-R3)
375
+ await elasticache.send(new CreateCacheClusterCommand({
376
+ CacheClusterId: clusterId,
377
+ CacheNodeType: cacheNodeType(ec2InstanceType),
378
+ Engine: 'redis',
379
+ NumCacheNodes: 1,
380
+ Tags: [
381
+ { Key: 'Name', Value: `${ctx.projectName}-redis` },
382
+ { Key: 'ManagedBy', Value: 'VoidForge' },
383
+ ],
384
+ }));
385
+ resources.push({ type: 'elasticache-cluster', id: clusterId, region });
386
+ await recordResourceCreated(ctx.runId, 'elasticache-cluster', clusterId, region);
387
+ outputs['REDIS_CLUSTER_ID'] = clusterId;
388
+ emit({ step: 'elasticache', status: 'done', message: `ElastiCache cluster "${clusterId}" created — waiting for endpoint` });
389
+ // Step 8b: Poll ElastiCache until available (non-fatal on timeout)
390
+ const CACHE_POLL_MS = 5000;
391
+ const CACHE_TIMEOUT_MS = 300000; // 5 minutes
392
+ const CACHE_PROGRESS_MS = 15000;
393
+ emit({ step: 'cache-wait', status: 'started', message: 'Waiting for Redis to become available (1-2 minutes)...' });
394
+ try {
395
+ const cacheStart = Date.now();
396
+ let lastCacheProgress = cacheStart;
397
+ let redisHost = '';
398
+ while (Date.now() - cacheStart < CACHE_TIMEOUT_MS) {
399
+ await cancellableSleep(CACHE_POLL_MS + Math.random() * 1000, ctx.abortSignal);
400
+ const desc = await elasticache.send(new DescribeCacheClustersCommand({
401
+ CacheClusterId: clusterId,
402
+ ShowCacheNodeInfo: true,
403
+ }));
404
+ const cluster = desc.CacheClusters?.[0];
405
+ const status = cluster?.CacheClusterStatus;
406
+ if (status === 'available') {
407
+ redisHost = cluster?.CacheNodes?.[0]?.Endpoint?.Address ?? '';
408
+ break;
409
+ }
410
+ // Check for terminal failure states
411
+ const cacheTerminalStates = ['deleted', 'deleting', 'create-failed', 'snapshotting'];
412
+ if (status && cacheTerminalStates.includes(status)) {
413
+ emit({ step: 'cache-wait', status: 'error', message: `Redis entered terminal state: ${status}`, detail: 'Check AWS Console for details' });
414
+ break;
415
+ }
416
+ if (Date.now() - lastCacheProgress >= CACHE_PROGRESS_MS) {
417
+ const elapsed = Math.round((Date.now() - cacheStart) / 1000);
418
+ emit({ step: 'cache-wait', status: 'started', message: `Redis status: ${status || 'creating'}... (${elapsed}s elapsed)` });
419
+ lastCacheProgress = Date.now();
420
+ }
421
+ }
422
+ if (redisHost) {
423
+ outputs['REDIS_HOST'] = redisHost;
424
+ outputs['REDIS_PORT'] = '6379';
425
+ emit({ step: 'cache-wait', status: 'done', message: `Redis available at ${redisHost}:6379` });
426
+ }
427
+ else {
428
+ emit({ step: 'cache-wait', status: 'error', message: 'Redis polling timed out after 5 minutes', detail: `Cluster "${clusterId}" is still provisioning. Check the AWS Console for the endpoint.` });
429
+ }
430
+ }
431
+ catch (pollErr) {
432
+ if (pollErr.message === 'Aborted') {
433
+ emit({ step: 'cache-wait', status: 'skipped', message: 'Redis polling cancelled' });
434
+ }
435
+ else {
436
+ console.error('Redis polling error:', pollErr.message);
437
+ emit({ step: 'cache-wait', status: 'error', message: 'Redis polling failed', detail: 'Check AWS Console for details' });
438
+ }
439
+ }
440
+ }
441
+ catch (err) {
442
+ console.error('ElastiCache creation error:', err.message);
443
+ emit({ step: 'elasticache', status: 'error', message: 'Failed to create ElastiCache cluster', detail: 'Check AWS Console for details' });
444
+ // Non-fatal
445
+ }
446
+ }
447
+ else {
448
+ emit({ step: 'elasticache', status: 'skipped', message: 'No cache requested' });
449
+ }
450
+ // Step 9: Generate infrastructure scripts
451
+ emit({ step: 'generate-scripts', status: 'started', message: 'Generating deploy scripts' });
452
+ try {
453
+ const infraDir = join(ctx.projectDir, 'infra');
454
+ await mkdir(infraDir, { recursive: true });
455
+ const framework = ctx.framework || 'express';
456
+ // provision.sh
457
+ const provisionSh = generateProvisionScript({ framework, database: ctx.database, cache: ctx.cache, instanceType: ec2InstanceType });
458
+ await writeFile(join(infraDir, 'provision.sh'), provisionSh, { mode: 0o755 });
459
+ files.push('infra/provision.sh');
460
+ // deploy.sh
461
+ const deploySh = generateDeployScript({ framework });
462
+ await writeFile(join(infraDir, 'deploy.sh'), deploySh, { mode: 0o755 });
463
+ files.push('infra/deploy.sh');
464
+ // rollback.sh
465
+ const rollbackSh = generateRollbackScript({ framework });
466
+ await writeFile(join(infraDir, 'rollback.sh'), rollbackSh, { mode: 0o755 });
467
+ files.push('infra/rollback.sh');
468
+ // Caddyfile
469
+ const caddyfile = generateCaddyfile({ framework, hostname: ctx.hostname || undefined });
470
+ await writeFile(join(infraDir, 'Caddyfile'), caddyfile, 'utf-8');
471
+ files.push('infra/Caddyfile');
472
+ // ecosystem.config.js (Node frameworks only)
473
+ if (['next.js', 'express'].includes(framework) || !framework) {
474
+ const ecosystem = generateEcosystemConfig({ projectName: ctx.projectName, framework });
475
+ await writeFile(join(ctx.projectDir, 'ecosystem.config.js'), ecosystem, 'utf-8');
476
+ files.push('ecosystem.config.js');
477
+ }
478
+ emit({ step: 'generate-scripts', status: 'done', message: `Generated ${files.length} infrastructure files` });
479
+ }
480
+ catch (err) {
481
+ console.error('Script generation error:', err.message);
482
+ emit({ step: 'generate-scripts', status: 'error', message: 'Failed to generate scripts', detail: 'Check AWS Console for details' });
483
+ return { success: false, resources, outputs, files, error: 'Failed to generate infrastructure scripts' };
484
+ }
485
+ // Step 10: Write .env with infrastructure details
486
+ emit({ step: 'write-env', status: 'started', message: 'Writing infrastructure config to .env' });
487
+ try {
488
+ const envLines = [
489
+ `# VoidForge Infrastructure — generated ${new Date().toISOString()}`,
490
+ `SSH_HOST=${publicIp}`,
491
+ `SSH_USER=ec2-user`,
492
+ `SSH_KEY_PATH=.ssh/deploy-key.pem`,
493
+ ];
494
+ if (outputs['DB_ENGINE']) {
495
+ envLines.push(`DB_ENGINE=${outputs['DB_ENGINE']}`);
496
+ envLines.push(`DB_HOST=${outputs['DB_HOST'] || `# pending — check https://${region}.console.aws.amazon.com/rds/home?region=${region}#databases:`}`);
497
+ envLines.push(`DB_PORT=${outputs['DB_PORT']}`);
498
+ envLines.push(`DB_INSTANCE_ID=${outputs['DB_INSTANCE_ID']}`);
499
+ envLines.push(`DB_USERNAME=${outputs['DB_USERNAME']}`);
500
+ envLines.push(`DB_PASSWORD=${outputs['DB_PASSWORD']}`);
501
+ }
502
+ if (outputs['REDIS_CLUSTER_ID']) {
503
+ envLines.push(`REDIS_CLUSTER_ID=${outputs['REDIS_CLUSTER_ID']}`);
504
+ envLines.push(`REDIS_HOST=${outputs['REDIS_HOST'] || `# pending — check https://${region}.console.aws.amazon.com/elasticache/home?region=${region}`}`);
505
+ envLines.push(`REDIS_PORT=${outputs['REDIS_PORT'] || '6379'}`);
506
+ }
507
+ await appendEnvSection(ctx.projectDir, envLines);
508
+ chmodSync(join(ctx.projectDir, '.env'), 0o600);
509
+ emit({ step: 'write-env', status: 'done', message: 'Infrastructure config written to .env' });
510
+ }
511
+ catch (err) {
512
+ console.error('Env file write error:', err.message);
513
+ emit({ step: 'write-env', status: 'error', message: 'Failed to write .env', detail: err.message });
514
+ // Non-fatal
515
+ }
516
+ // DEVOPS-R2-001: Restrict SSH from 0.0.0.0/0 to deployer's IP after provisioning
517
+ try {
518
+ const { EC2Client, RevokeSecurityGroupIngressCommand, AuthorizeSecurityGroupIngressCommand } = await import('@aws-sdk/client-ec2');
519
+ const ec2Restrict = new EC2Client(awsConfig);
520
+ // Detect deployer's public IP via checkip.amazonaws.com
521
+ let deployerIp = null;
522
+ try {
523
+ const ipRes = await fetch('https://checkip.amazonaws.com', { signal: AbortSignal.timeout(5000) });
524
+ if (ipRes.ok)
525
+ deployerIp = (await ipRes.text()).trim();
526
+ }
527
+ catch { /* non-fatal — keep 0.0.0.0/0 if detection fails */ }
528
+ if (deployerIp && /^\d+\.\d+\.\d+\.\d+$/.test(deployerIp)) {
529
+ // Revoke the wide-open SSH rule
530
+ await ec2Restrict.send(new RevokeSecurityGroupIngressCommand({
531
+ GroupId: sgId,
532
+ IpPermissions: [{ IpProtocol: 'tcp', FromPort: 22, ToPort: 22, IpRanges: [{ CidrIp: '0.0.0.0/0' }] }],
533
+ }));
534
+ // Add restricted SSH rule for deployer's IP only
535
+ await ec2Restrict.send(new AuthorizeSecurityGroupIngressCommand({
536
+ GroupId: sgId,
537
+ IpPermissions: [{ IpProtocol: 'tcp', FromPort: 22, ToPort: 22, IpRanges: [{ CidrIp: `${deployerIp}/32`, Description: 'SSH (deployer IP)' }] }],
538
+ }));
539
+ emit({ step: 'ssh-restrict', status: 'done', message: `SSH restricted to ${deployerIp}/32 (was 0.0.0.0/0)` });
540
+ }
541
+ else {
542
+ emit({ step: 'ssh-restrict', status: 'warning', message: 'Could not detect public IP — SSH remains open to 0.0.0.0/0. Restrict manually in AWS Console.' });
543
+ }
544
+ }
545
+ catch (err) {
546
+ emit({ step: 'ssh-restrict', status: 'warning', message: 'SSH restriction failed (non-fatal). Restrict port 22 manually.', detail: err.message });
547
+ }
548
+ return { success: true, resources, outputs, files };
549
+ },
550
+ async cleanup(resources, credentials) {
551
+ if (resources.length === 0)
552
+ return;
553
+ const region = resources[0].region;
554
+ const awsConfig = {
555
+ region,
556
+ credentials: {
557
+ accessKeyId: credentials['aws-access-key-id'] ?? '',
558
+ secretAccessKey: credentials['aws-secret-access-key'] ?? '',
559
+ },
560
+ };
561
+ // Clean up in reverse order
562
+ for (const resource of [...resources].reverse()) {
563
+ try {
564
+ switch (resource.type) {
565
+ case 'ec2-instance': {
566
+ const { EC2Client, TerminateInstancesCommand } = await import('@aws-sdk/client-ec2');
567
+ const ec2 = new EC2Client(awsConfig);
568
+ await ec2.send(new TerminateInstancesCommand({ InstanceIds: [resource.id] }));
569
+ break;
570
+ }
571
+ case 'security-group': {
572
+ const { EC2Client, DeleteSecurityGroupCommand, DescribeInstancesCommand: DescInst } = await import('@aws-sdk/client-ec2');
573
+ const ec2 = new EC2Client(awsConfig);
574
+ // Wait for all instances in the SG to terminate before deleting
575
+ const maxWait = 120000; // 2 minutes
576
+ const start = Date.now();
577
+ while (Date.now() - start < maxWait) {
578
+ await sleep(10000);
579
+ try {
580
+ await ec2.send(new DeleteSecurityGroupCommand({ GroupId: resource.id }));
581
+ break; // Success — SG deleted
582
+ }
583
+ catch (sgErr) {
584
+ const msg = sgErr.message || '';
585
+ if (msg.includes('DependencyViolation')) {
586
+ continue; // Instance still terminating, retry
587
+ }
588
+ throw sgErr; // Different error — propagate
589
+ }
590
+ }
591
+ break;
592
+ }
593
+ case 'key-pair': {
594
+ const { EC2Client, DeleteKeyPairCommand } = await import('@aws-sdk/client-ec2');
595
+ const ec2 = new EC2Client(awsConfig);
596
+ await ec2.send(new DeleteKeyPairCommand({ KeyName: resource.id }));
597
+ break;
598
+ }
599
+ case 'rds-instance': {
600
+ const { RDSClient, DeleteDBInstanceCommand } = await import('@aws-sdk/client-rds');
601
+ const rds = new RDSClient(awsConfig);
602
+ try {
603
+ await rds.send(new DeleteDBInstanceCommand({
604
+ DBInstanceIdentifier: resource.id,
605
+ SkipFinalSnapshot: true,
606
+ }));
607
+ }
608
+ catch (rdsErr) {
609
+ const code = rdsErr.name ?? '';
610
+ if (code === 'InvalidDBInstanceState') {
611
+ console.error(`RDS instance "${resource.id}" is still creating — check AWS Console in 10 minutes to delete manually.`);
612
+ }
613
+ else {
614
+ throw rdsErr;
615
+ }
616
+ }
617
+ break;
618
+ }
619
+ case 'elasticache-cluster': {
620
+ const { ElastiCacheClient, DeleteCacheClusterCommand } = await import('@aws-sdk/client-elasticache');
621
+ const ec = new ElastiCacheClient(awsConfig);
622
+ try {
623
+ await ec.send(new DeleteCacheClusterCommand({ CacheClusterId: resource.id }));
624
+ }
625
+ catch (cacheErr) {
626
+ const code = cacheErr.name ?? '';
627
+ if (code === 'InvalidCacheClusterState') {
628
+ console.error(`ElastiCache cluster "${resource.id}" is still creating — check AWS Console in 10 minutes to delete manually.`);
629
+ }
630
+ else {
631
+ throw cacheErr;
632
+ }
633
+ }
634
+ break;
635
+ }
636
+ }
637
+ }
638
+ catch (err) {
639
+ console.error(`Failed to cleanup ${resource.type} ${resource.id}:`, err.message);
640
+ }
641
+ }
642
+ },
643
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Cloudflare provisioner — creates a real Workers/Pages project via API + generates wrangler.toml.
3
+ * v3.8.0: Includes GitHub source at creation time for auto-deploy (ADR-011, ADR-015).
4
+ */
5
+ import type { Provisioner } from './types.js';
6
+ export declare const cloudflareProvisioner: Provisioner;