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,262 @@
1
+ /**
2
+ * Torres's Site Scanner — HTTP-based technical reconnaissance.
3
+ *
4
+ * Scans a deployed site without browser dependencies:
5
+ * - Response times and status codes on main routes
6
+ * - Meta tags (title, description, OG, JSON-LD)
7
+ * - Security headers (HTTPS, HSTS, CSP, CORS, X-Frame-Options)
8
+ * - Cache headers, compression (gzip/brotli)
9
+ * - Analytics detection (GA4, Plausible, PostHog snippets)
10
+ * - Mobile viewport, favicon, sitemap.xml, robots.txt
11
+ * - Growth infrastructure (email capture forms, cookie consent)
12
+ *
13
+ * Zero dependencies — uses node:https only.
14
+ *
15
+ * PRD Reference: ROADMAP v12.0 deliverables (Torres's site scanner)
16
+ */
17
+ import { get as httpsGet } from 'node:https';
18
+ import { get as httpGet } from 'node:http';
19
+ import { URL } from 'node:url';
20
+ // ── HTTP Helper ───────────────────────────────────────
21
+ /** SSRF protection: reject private/internal IP ranges + DNS rebinding defense */
22
+ function isPrivateIp(ip) {
23
+ // Block loopback, private ranges, link-local, metadata endpoints, mapped IPv6
24
+ if (/^(127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.|0x|::1|::ffff:(127|10|172\.(1[6-9]|2|3[01])|192\.168|169\.254)|fd|fe80)/i.test(ip))
25
+ return true;
26
+ if (ip === 'localhost' || ip === '::1' || ip === '[::1]' || ip === '0.0.0.0')
27
+ return true;
28
+ return false;
29
+ }
30
+ function isPrivateUrl(url) {
31
+ try {
32
+ const hostname = new URL(url).hostname;
33
+ // First check: hostname string (catches literal IPs and localhost)
34
+ if (isPrivateIp(hostname))
35
+ return true;
36
+ // DNS rebinding defense: resolve hostname and check resolved IP
37
+ // Note: full DNS resolution requires async — for the sync check, we block
38
+ // known-bad hostnames. The async fetchUrl also checks after connection.
39
+ if (/^(metadata|instance-data)/i.test(hostname))
40
+ return true; // Cloud metadata hostnames
41
+ return false;
42
+ }
43
+ catch {
44
+ return true;
45
+ } // Malformed URL = reject
46
+ }
47
+ function fetchUrl(url, timeoutMs = 10000, maxRedirects = 5) {
48
+ return new Promise((resolve, reject) => {
49
+ if (isPrivateUrl(url)) {
50
+ reject(new Error(`SSRF blocked: ${new URL(url).hostname} is a private/internal address`));
51
+ return;
52
+ }
53
+ const parsedUrl = new URL(url);
54
+ const getter = parsedUrl.protocol === 'https:' ? httpsGet : httpGet;
55
+ const start = Date.now();
56
+ let ttfb = 0;
57
+ const req = getter(url, { timeout: timeoutMs, headers: { 'User-Agent': 'VoidForge-Scanner/1.0' } }, (res) => {
58
+ ttfb = Date.now() - start;
59
+ let body = '';
60
+ // Follow redirects with depth limit
61
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
62
+ if (maxRedirects <= 0) {
63
+ reject(new Error('Too many redirects (max 5)'));
64
+ return;
65
+ }
66
+ const redirectUrl = new URL(res.headers.location, url).toString();
67
+ fetchUrl(redirectUrl, timeoutMs, maxRedirects - 1).then(resolve).catch(reject);
68
+ return;
69
+ }
70
+ res.setEncoding('utf-8');
71
+ res.on('data', (chunk) => { body += chunk; });
72
+ res.on('end', () => {
73
+ resolve({
74
+ statusCode: res.statusCode || 0,
75
+ headers: res.headers,
76
+ body: body.slice(0, 500000), // Cap at 500KB to prevent memory issues
77
+ ttfbMs: ttfb,
78
+ totalMs: Date.now() - start,
79
+ });
80
+ });
81
+ });
82
+ req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
83
+ req.on('error', reject);
84
+ });
85
+ }
86
+ async function checkExists(url) {
87
+ try {
88
+ const res = await fetchUrl(url, 5000);
89
+ return res.statusCode === 200;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ // ── HTML Parsing (zero-dep, regex-based) ──────────────
96
+ function extractMeta(html, name) {
97
+ const re = new RegExp(`<meta[^>]+(?:name|property)=["']${name}["'][^>]+content=["']([^"']+)["']`, 'i');
98
+ const alt = new RegExp(`<meta[^>]+content=["']([^"']+)["'][^>]+(?:name|property)=["']${name}["']`, 'i');
99
+ return (html.match(re)?.[1] || html.match(alt)?.[1]) ?? null;
100
+ }
101
+ function extractTitle(html) {
102
+ return html.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]?.trim() ?? null;
103
+ }
104
+ function countTag(html, tag) {
105
+ const re = new RegExp(`<${tag}[\\s>]`, 'gi');
106
+ return (html.match(re) || []).length;
107
+ }
108
+ function hasTag(html, pattern) {
109
+ return new RegExp(pattern, 'i').test(html);
110
+ }
111
+ // ── Main Scanner ──────────────────────────────────────
112
+ export async function scanSite(siteUrl) {
113
+ const result = {
114
+ url: siteUrl,
115
+ scannedAt: new Date().toISOString(),
116
+ reachable: false,
117
+ performance: { ttfbMs: null, totalTimeMs: null, contentLength: null, compressed: false, cacheControl: null },
118
+ seo: { title: null, description: null, ogTitle: null, ogDescription: null, ogImage: null, canonicalUrl: null, viewport: false, favicon: false, jsonLd: false, sitemapExists: false, robotsExists: false, h1Count: 0 },
119
+ security: { https: false, hsts: false, csp: null, xFrameOptions: null, xContentTypeOptions: false, referrerPolicy: null, corsAllowOrigin: null },
120
+ growth: { analyticsDetected: [], cookieConsentDetected: false, emailCaptureDetected: false, socialMetaComplete: false },
121
+ health: { statusCode: null, redirectChain: [], responseHeaders: {} },
122
+ };
123
+ try {
124
+ const res = await fetchUrl(siteUrl);
125
+ result.reachable = true;
126
+ const html = res.body;
127
+ const headers = res.headers;
128
+ // ── Performance ────────────────
129
+ result.performance.ttfbMs = res.ttfbMs;
130
+ result.performance.totalTimeMs = res.totalMs;
131
+ result.performance.contentLength = parseInt(headers['content-length'] || '0') || html.length;
132
+ result.performance.compressed = !!(headers['content-encoding']?.match(/gzip|br|deflate/));
133
+ result.performance.cacheControl = headers['cache-control'] ?? null;
134
+ // ── SEO ────────────────────────
135
+ result.seo.title = extractTitle(html);
136
+ result.seo.description = extractMeta(html, 'description');
137
+ result.seo.ogTitle = extractMeta(html, 'og:title');
138
+ result.seo.ogDescription = extractMeta(html, 'og:description');
139
+ result.seo.ogImage = extractMeta(html, 'og:image');
140
+ result.seo.canonicalUrl = html.match(/<link[^>]+rel=["']canonical["'][^>]+href=["']([^"']+)["']/i)?.[1] ?? null;
141
+ result.seo.viewport = hasTag(html, '<meta[^>]+name=["\'"]viewport');
142
+ result.seo.favicon = hasTag(html, '<link[^>]+rel=["\'"](?:icon|shortcut icon)');
143
+ result.seo.jsonLd = hasTag(html, '<script[^>]+type=["\'"]application/ld\\+json');
144
+ result.seo.h1Count = countTag(html, 'h1');
145
+ // Check sitemap and robots
146
+ const baseUrl = new URL(siteUrl).origin;
147
+ const [sitemapOk, robotsOk] = await Promise.all([
148
+ checkExists(baseUrl + '/sitemap.xml'),
149
+ checkExists(baseUrl + '/robots.txt'),
150
+ ]);
151
+ result.seo.sitemapExists = sitemapOk;
152
+ result.seo.robotsExists = robotsOk;
153
+ // ── Security ───────────────────
154
+ result.security.https = siteUrl.startsWith('https://');
155
+ result.security.hsts = !!headers['strict-transport-security'];
156
+ result.security.csp = headers['content-security-policy'] ?? null;
157
+ result.security.xFrameOptions = headers['x-frame-options'] ?? null;
158
+ result.security.xContentTypeOptions = headers['x-content-type-options'] === 'nosniff';
159
+ result.security.referrerPolicy = headers['referrer-policy'] ?? null;
160
+ result.security.corsAllowOrigin = headers['access-control-allow-origin'] ?? null;
161
+ // ── Growth ─────────────────────
162
+ if (hasTag(html, 'gtag|GA4|googletagmanager|G-[A-Z0-9]+'))
163
+ result.growth.analyticsDetected.push('ga4');
164
+ if (hasTag(html, 'plausible'))
165
+ result.growth.analyticsDetected.push('plausible');
166
+ if (hasTag(html, 'posthog'))
167
+ result.growth.analyticsDetected.push('posthog');
168
+ if (hasTag(html, 'hotjar'))
169
+ result.growth.analyticsDetected.push('hotjar');
170
+ if (hasTag(html, 'mixpanel'))
171
+ result.growth.analyticsDetected.push('mixpanel');
172
+ result.growth.cookieConsentDetected = hasTag(html, 'cookie.?consent|cookie.?banner|cookiebot|onetrust');
173
+ result.growth.emailCaptureDetected = hasTag(html, '<input[^>]+type=["\'"]email');
174
+ result.growth.socialMetaComplete = !!(result.seo.ogTitle && result.seo.ogDescription && result.seo.ogImage && extractMeta(html, 'twitter:card'));
175
+ // ── Health ─────────────────────
176
+ result.health.statusCode = res.statusCode;
177
+ result.health.responseHeaders = headers;
178
+ }
179
+ catch (err) {
180
+ result.error = err.message;
181
+ }
182
+ return result;
183
+ }
184
+ /**
185
+ * Score the scan result across the 5 Deep Current dimensions.
186
+ * Returns partial scores for performance, SEO (part of growth), and security.
187
+ */
188
+ export function scoreScan(scan) {
189
+ if (!scan.reachable)
190
+ return { performance: 0, seoScore: 0, securityScore: 0, growthReadiness: 0 };
191
+ // Performance (0-100)
192
+ let perf = 50; // base
193
+ if (scan.performance.ttfbMs !== null) {
194
+ if (scan.performance.ttfbMs < 200)
195
+ perf += 20;
196
+ else if (scan.performance.ttfbMs < 800)
197
+ perf += 10;
198
+ else
199
+ perf -= 10;
200
+ }
201
+ if (scan.performance.compressed)
202
+ perf += 15;
203
+ if (scan.performance.cacheControl)
204
+ perf += 15;
205
+ perf = Math.max(0, Math.min(100, perf));
206
+ // SEO (0-100)
207
+ let seo = 0;
208
+ if (scan.seo.title)
209
+ seo += 15;
210
+ if (scan.seo.description)
211
+ seo += 15;
212
+ if (scan.seo.viewport)
213
+ seo += 10;
214
+ if (scan.seo.favicon)
215
+ seo += 5;
216
+ if (scan.seo.sitemapExists)
217
+ seo += 15;
218
+ if (scan.seo.robotsExists)
219
+ seo += 10;
220
+ if (scan.seo.jsonLd)
221
+ seo += 10;
222
+ if (scan.seo.canonicalUrl)
223
+ seo += 10;
224
+ if (scan.seo.h1Count === 1)
225
+ seo += 10; // Exactly one h1 is best practice
226
+ // Security (0-100)
227
+ let sec = 0;
228
+ if (scan.security.https)
229
+ sec += 25;
230
+ if (scan.security.hsts)
231
+ sec += 15;
232
+ if (scan.security.csp)
233
+ sec += 20;
234
+ if (scan.security.xFrameOptions)
235
+ sec += 10;
236
+ if (scan.security.xContentTypeOptions)
237
+ sec += 10;
238
+ if (scan.security.referrerPolicy)
239
+ sec += 10;
240
+ sec += 10; // base for being reachable
241
+ // Growth Readiness (0-100)
242
+ let growth = 0;
243
+ if (scan.growth.analyticsDetected.length > 0)
244
+ growth += 25;
245
+ if (scan.growth.socialMetaComplete)
246
+ growth += 20;
247
+ if (scan.growth.emailCaptureDetected)
248
+ growth += 20;
249
+ if (scan.growth.cookieConsentDetected)
250
+ growth += 10;
251
+ if (scan.seo.sitemapExists)
252
+ growth += 10;
253
+ if (scan.seo.jsonLd)
254
+ growth += 10;
255
+ growth += 5; // base
256
+ return {
257
+ performance: Math.min(100, perf),
258
+ seoScore: Math.min(100, seo),
259
+ securityScore: Math.min(100, sec),
260
+ growthReadiness: Math.min(100, growth),
261
+ };
262
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SSH deploy — connect to EC2, run provision.sh, deploy code via release directories.
3
+ * Uses exec.ts for child process operations (ADR-013).
4
+ * Performs release-directory management directly via SSH (not deploy.sh which is local-mode).
5
+ * Retry loop handles sshd startup delay after EC2 launch.
6
+ */
7
+ import type { ProvisionEmitter } from './provisioners/types.js';
8
+ export interface SshDeployResult {
9
+ success: boolean;
10
+ deployUrl?: string;
11
+ error?: string;
12
+ }
13
+ /**
14
+ * Full SSH deploy flow for AWS VPS:
15
+ * 1. Validate ssh/rsync binaries
16
+ * 2. Upload provision.sh via rsync
17
+ * 3. Run provision.sh (first-time server setup)
18
+ * 4. Create release directory, rsync code, install deps, swap symlink, restart
19
+ * 5. Health check with rollback on failure
20
+ *
21
+ * NOTE: deploy.sh is a LOCAL-mode script (runs from dev machine, SSHes into server).
22
+ * This module runs commands DIRECTLY on the server via SSH, matching the same
23
+ * release-directory strategy but without the SSH-over-SSH problem.
24
+ */
25
+ export declare function sshDeploy(projectDir: string, host: string, user: string, keyPath: string, hostname: string | undefined, framework: string, emit: ProvisionEmitter, abortSignal?: AbortSignal): Promise<SshDeployResult>;
@@ -0,0 +1,225 @@
1
+ /**
2
+ * SSH deploy — connect to EC2, run provision.sh, deploy code via release directories.
3
+ * Uses exec.ts for child process operations (ADR-013).
4
+ * Performs release-directory management directly via SSH (not deploy.sh which is local-mode).
5
+ * Retry loop handles sshd startup delay after EC2 launch.
6
+ */
7
+ import { join } from 'node:path';
8
+ import { execCommand, validateBinaries } from './exec.js';
9
+ const SSH_RETRY_COUNT = 5;
10
+ const SSH_RETRY_DELAY_MS = 10_000;
11
+ const SSH_COMMAND_TIMEOUT_MS = 300_000; // 5 minutes per command
12
+ const HEALTH_CHECK_RETRIES = 5;
13
+ const HEALTH_CHECK_DELAY_MS = 5_000;
14
+ function sleep(ms) {
15
+ return new Promise(r => setTimeout(r, ms));
16
+ }
17
+ function sshArgs(keyPath, host, user) {
18
+ return [
19
+ '-i', keyPath,
20
+ '-o', 'StrictHostKeyChecking=accept-new',
21
+ '-o', 'ConnectTimeout=10',
22
+ '-o', 'BatchMode=yes',
23
+ `${user}@${host}`,
24
+ ];
25
+ }
26
+ /**
27
+ * Execute a single SSH command with retry for connection failures.
28
+ * Returns stdout on success, throws on failure after all retries.
29
+ */
30
+ async function sshExec(keyPath, host, user, command, emit, stepName, abortSignal) {
31
+ for (let attempt = 1; attempt <= SSH_RETRY_COUNT; attempt++) {
32
+ try {
33
+ const result = await execCommand('ssh', [...sshArgs(keyPath, host, user), command], { timeout: SSH_COMMAND_TIMEOUT_MS, abortSignal });
34
+ return result.stdout;
35
+ }
36
+ catch (err) {
37
+ const msg = err.message;
38
+ const isConnectionError = msg.includes('Connection refused') ||
39
+ msg.includes('Connection timed out') ||
40
+ msg.includes('No route to host') ||
41
+ msg.includes('Connection reset');
42
+ if (isConnectionError && attempt < SSH_RETRY_COUNT) {
43
+ emit({
44
+ step: stepName,
45
+ status: 'started',
46
+ message: `SSH connection failed (attempt ${attempt}/${SSH_RETRY_COUNT}), retrying in ${SSH_RETRY_DELAY_MS / 1000}s...`,
47
+ });
48
+ await sleep(SSH_RETRY_DELAY_MS);
49
+ continue;
50
+ }
51
+ throw err;
52
+ }
53
+ }
54
+ throw new Error('SSH retry loop exited unexpectedly');
55
+ }
56
+ /**
57
+ * Full SSH deploy flow for AWS VPS:
58
+ * 1. Validate ssh/rsync binaries
59
+ * 2. Upload provision.sh via rsync
60
+ * 3. Run provision.sh (first-time server setup)
61
+ * 4. Create release directory, rsync code, install deps, swap symlink, restart
62
+ * 5. Health check with rollback on failure
63
+ *
64
+ * NOTE: deploy.sh is a LOCAL-mode script (runs from dev machine, SSHes into server).
65
+ * This module runs commands DIRECTLY on the server via SSH, matching the same
66
+ * release-directory strategy but without the SSH-over-SSH problem.
67
+ */
68
+ export async function sshDeploy(projectDir, host, user, keyPath, hostname, framework, emit, abortSignal) {
69
+ // Step 1: Validate binaries
70
+ emit({ step: 'ssh-validate', status: 'started', message: 'Checking for ssh and rsync' });
71
+ const missingBins = await validateBinaries(['ssh', 'rsync']);
72
+ if (missingBins.length > 0) {
73
+ emit({ step: 'ssh-validate', status: 'error', message: `Missing binaries: ${missingBins.join(', ')}. Install them to enable SSH deploy.` });
74
+ return { success: false, error: `Missing: ${missingBins.join(', ')}` };
75
+ }
76
+ emit({ step: 'ssh-validate', status: 'done', message: 'ssh and rsync found' });
77
+ const fullKeyPath = keyPath.startsWith('/') ? keyPath : join(projectDir, keyPath);
78
+ const rsyncSshFlag = `ssh -i "${fullKeyPath}" -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10`;
79
+ // Step 2: Upload infrastructure scripts
80
+ emit({ step: 'ssh-upload', status: 'started', message: 'Uploading infrastructure scripts' });
81
+ try {
82
+ await sshExec(fullKeyPath, host, user, 'mkdir -p ~/infra', emit, 'ssh-upload', abortSignal);
83
+ await execCommand('rsync', [
84
+ '-avz', '--progress',
85
+ '-e', rsyncSshFlag,
86
+ `${join(projectDir, 'infra')}/`,
87
+ `${user}@${host}:~/infra/`,
88
+ ], { timeout: 60_000, abortSignal });
89
+ emit({ step: 'ssh-upload', status: 'done', message: 'Infrastructure scripts uploaded' });
90
+ }
91
+ catch (err) {
92
+ emit({ step: 'ssh-upload', status: 'error', message: 'Failed to upload scripts', detail: err.message });
93
+ return { success: false, error: err.message };
94
+ }
95
+ // Step 3: Run provision.sh (first-time server setup — idempotent, safe to re-run)
96
+ emit({ step: 'ssh-provision', status: 'started', message: 'Running server provisioning (this may take 2-5 minutes)...' });
97
+ try {
98
+ const provStart = Date.now();
99
+ const heartbeat = setInterval(() => {
100
+ const elapsed = Math.round((Date.now() - provStart) / 1000);
101
+ emit({ step: 'ssh-provision', status: 'started', message: `Still provisioning... (${elapsed}s elapsed)` });
102
+ }, 30_000);
103
+ try {
104
+ await sshExec(fullKeyPath, host, user, 'chmod +x ~/infra/provision.sh && sudo ~/infra/provision.sh', emit, 'ssh-provision', abortSignal);
105
+ }
106
+ finally {
107
+ clearInterval(heartbeat);
108
+ }
109
+ emit({ step: 'ssh-provision', status: 'done', message: 'Server provisioning complete' });
110
+ }
111
+ catch (err) {
112
+ emit({ step: 'ssh-provision', status: 'error', message: 'Server provisioning failed', detail: err.message });
113
+ return { success: false, error: `provision.sh failed: ${err.message}` };
114
+ }
115
+ // Step 4: Deploy code using release-directory strategy
116
+ // Matches the deploy.sh pattern but executed server-side via SSH commands
117
+ const release = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
118
+ const releasesDir = '/opt/app/releases';
119
+ const currentLink = '/opt/app/current';
120
+ const releaseDir = `${releasesDir}/${release}`;
121
+ emit({ step: 'ssh-deploy', status: 'started', message: `Creating release ${release}` });
122
+ let previousRelease = '';
123
+ try {
124
+ // Create release directory
125
+ await sshExec(fullKeyPath, host, user, `sudo mkdir -p ${releaseDir} && sudo chown ${user}:${user} ${releaseDir}`, emit, 'ssh-deploy', abortSignal);
126
+ // Rsync project code to release directory (excluding dev artifacts)
127
+ await execCommand('rsync', [
128
+ '-avz', '--delete',
129
+ '--exclude', '.git',
130
+ '--exclude', 'node_modules',
131
+ '--exclude', '.env',
132
+ '--exclude', '.ssh',
133
+ '--exclude', 'infra',
134
+ '--exclude', 'coverage',
135
+ '--exclude', 'logs',
136
+ '-e', rsyncSshFlag,
137
+ `${projectDir}/`,
138
+ `${user}@${host}:${releaseDir}/`,
139
+ ], { timeout: 120_000, abortSignal });
140
+ emit({ step: 'ssh-deploy', status: 'started', message: 'Code uploaded, installing dependencies...' });
141
+ // Install dependencies
142
+ const isNode = ['next.js', 'express'].includes(framework) || !framework;
143
+ const isDjango = framework === 'django';
144
+ const installCmd = isNode
145
+ ? 'npm ci --omit=dev'
146
+ : isDjango
147
+ ? 'pip3.12 install -r requirements.txt'
148
+ : 'bundle install --deployment --without development test';
149
+ await sshExec(fullKeyPath, host, user, `cd ${releaseDir} && ${installCmd}`, emit, 'ssh-deploy', abortSignal);
150
+ // Save previous release for rollback
151
+ try {
152
+ previousRelease = (await sshExec(fullKeyPath, host, user, `readlink ${currentLink} 2>/dev/null || echo ''`, emit, 'ssh-deploy', abortSignal)).trim();
153
+ }
154
+ catch { /* no previous release */ }
155
+ // Swap symlink atomically
156
+ await sshExec(fullKeyPath, host, user, `sudo ln -sfn ${releaseDir} ${currentLink}`, emit, 'ssh-deploy', abortSignal);
157
+ // Restart application
158
+ emit({ step: 'ssh-deploy', status: 'started', message: 'Restarting application...' });
159
+ const restartCmd = isNode
160
+ ? `cd ${currentLink} && pm2 startOrRestart ${currentLink}/ecosystem.config.js --env production && pm2 save`
161
+ : isDjango
162
+ ? `cd ${currentLink} && python3.12 manage.py migrate --noinput && python3.12 manage.py collectstatic --noinput && supervisorctl restart app`
163
+ : `cd ${currentLink} && RAILS_ENV=production bundle exec rails db:migrate && touch tmp/restart.txt`;
164
+ await sshExec(fullKeyPath, host, user, restartCmd, emit, 'ssh-deploy', abortSignal);
165
+ emit({ step: 'ssh-deploy', status: 'done', message: 'Application deployed and restarted' });
166
+ }
167
+ catch (err) {
168
+ // Rollback: restore previous symlink if available
169
+ if (previousRelease) {
170
+ emit({ step: 'ssh-rollback', status: 'started', message: 'Deployment failed — rolling back to previous release' });
171
+ try {
172
+ await sshExec(fullKeyPath, host, user, `sudo ln -sfn ${previousRelease} ${currentLink}`, emit, 'ssh-rollback', abortSignal);
173
+ const isNode = ['next.js', 'express'].includes(framework) || !framework;
174
+ if (isNode) {
175
+ await sshExec(fullKeyPath, host, user, `cd ${currentLink} && pm2 startOrRestart ${currentLink}/ecosystem.config.js --env production`, emit, 'ssh-rollback', abortSignal);
176
+ }
177
+ emit({ step: 'ssh-rollback', status: 'done', message: `Rolled back to ${previousRelease}` });
178
+ }
179
+ catch {
180
+ emit({ step: 'ssh-rollback', status: 'error', message: 'Rollback also failed — check server manually' });
181
+ }
182
+ }
183
+ emit({ step: 'ssh-deploy', status: 'error', message: 'Deployment failed', detail: err.message });
184
+ return { success: false, error: err.message };
185
+ }
186
+ // Step 5: Health check
187
+ const deployUrl = hostname ? `https://${hostname}` : `http://${host}`;
188
+ emit({ step: 'ssh-health', status: 'started', message: `Checking health at ${deployUrl}` });
189
+ let healthy = false;
190
+ for (let i = 1; i <= HEALTH_CHECK_RETRIES; i++) {
191
+ try {
192
+ await sshExec(fullKeyPath, host, user, 'curl -sf http://localhost:3000/ -o /dev/null', emit, 'ssh-health', abortSignal);
193
+ healthy = true;
194
+ emit({ step: 'ssh-health', status: 'done', message: `Health check passed — live at ${deployUrl}` });
195
+ break;
196
+ }
197
+ catch {
198
+ if (i < HEALTH_CHECK_RETRIES) {
199
+ emit({ step: 'ssh-health', status: 'started', message: `Health check attempt ${i}/${HEALTH_CHECK_RETRIES} failed, retrying...` });
200
+ await sleep(HEALTH_CHECK_DELAY_MS);
201
+ }
202
+ }
203
+ }
204
+ if (!healthy) {
205
+ // Rollback on failed health check
206
+ if (previousRelease) {
207
+ emit({ step: 'ssh-rollback', status: 'started', message: 'Health check failed — rolling back' });
208
+ try {
209
+ await sshExec(fullKeyPath, host, user, `sudo ln -sfn ${previousRelease} ${currentLink}`, emit, 'ssh-rollback', abortSignal);
210
+ emit({ step: 'ssh-rollback', status: 'done', message: `Rolled back to ${previousRelease}` });
211
+ }
212
+ catch {
213
+ emit({ step: 'ssh-rollback', status: 'error', message: 'Rollback failed — check server manually' });
214
+ }
215
+ }
216
+ emit({ step: 'ssh-health', status: 'error', message: `Health check failed after ${HEALTH_CHECK_RETRIES} attempts`, detail: `Check: ssh -i ${keyPath} ${user}@${host}` });
217
+ return { success: false, deployUrl, error: 'Health check failed' };
218
+ }
219
+ // Clean up old releases (keep last 5)
220
+ try {
221
+ await sshExec(fullKeyPath, host, user, `cd ${releasesDir} && ls -1d */ 2>/dev/null | head -n -5 | xargs -r rm -rf`, emit, 'ssh-cleanup', abortSignal);
222
+ }
223
+ catch { /* non-fatal */ }
224
+ return { success: true, deployUrl };
225
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Project templates — curated starters with pre-filled PRD frontmatter,
3
+ * recommended integrations, and seed configuration.
4
+ *
5
+ * Usage: `npx voidforge init --template saas`
6
+ * Or selected in Gandalf wizard Step 4 (PRD tab: "Start from a template")
7
+ */
8
+ export interface ProjectTemplate {
9
+ id: string;
10
+ name: string;
11
+ description: string;
12
+ frontmatter: Record<string, string>;
13
+ suggestedIntegrations: string[];
14
+ prdSections: string;
15
+ }
16
+ export declare const TEMPLATES: ProjectTemplate[];
17
+ /** Get a template by ID. Returns null if not found. */
18
+ export declare function getTemplate(id: string): ProjectTemplate | null;
19
+ /** List all available template IDs with descriptions. */
20
+ export declare function listTemplates(): {
21
+ id: string;
22
+ name: string;
23
+ description: string;
24
+ }[];