@jini-ai/agent-plugins 0.6.16 → 0.6.19

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 (335) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/NOTICE +14 -0
  3. package/README.md +35 -14
  4. package/charts-echarts/plugin.json +39 -0
  5. package/charts-echarts/skills/charts-echarts/SKILL.md +110 -0
  6. package/charts-recharts/plugin.json +32 -0
  7. package/charts-recharts/skills/charts-recharts/SKILL.md +108 -0
  8. package/cloudflare/NOTES.md +16 -0
  9. package/cloudflare/form-destinations/cloudflare-d1.mjs +9 -0
  10. package/cloudflare/lib/api.mjs +96 -0
  11. package/cloudflare/lib/assets.mjs +65 -0
  12. package/cloudflare/lib/dns.mjs +32 -0
  13. package/cloudflare/lib/domain-inventory.mjs +61 -0
  14. package/cloudflare/lib/domain-status.mjs +48 -0
  15. package/cloudflare/lib/domains.mjs +95 -0
  16. package/cloudflare/lib/forms.mjs +254 -0
  17. package/cloudflare/lib/pages-upload.mjs +51 -0
  18. package/cloudflare/lib/target.mjs +165 -0
  19. package/cloudflare/lib/workers-upload.mjs +48 -0
  20. package/cloudflare/lib/zones.mjs +25 -0
  21. package/cloudflare/mcp.json +4 -0
  22. package/cloudflare/plugin.json +96 -0
  23. package/cloudflare/skills/cloudflare/SKILL.md +57 -0
  24. package/cloudflare/skills/cloudflare/references/domains.md +31 -0
  25. package/cloudflare/skills/cloudflare/references/pages.md +18 -0
  26. package/cloudflare/skills/cloudflare/references/permissions.md +29 -0
  27. package/cloudflare/skills/cloudflare/references/troubleshooting.md +20 -0
  28. package/cloudflare/skills/cloudflare/references/workers.md +42 -0
  29. package/cloudflare/targets/cloudflare-pages.mjs +12 -0
  30. package/cloudflare/targets/cloudflare-workers.mjs +17 -0
  31. package/cloudflare/tovu-consent.json +6 -0
  32. package/cloudflare/tovu-deploy-targets.json +630 -0
  33. package/cloudflare/tovu-form-destinations.json +12 -0
  34. package/cloudflare/worker/forms-receiver.mjs +249 -0
  35. package/composio/mcp.json +10 -0
  36. package/composio/plugin.json +68 -0
  37. package/composio/skills/composio/SKILL.md +85 -0
  38. package/dist/core/identity-namespace.d.ts +12 -0
  39. package/dist/core/identity-namespace.js +20 -0
  40. package/dist/folder-zip-node.js +29 -7
  41. package/dist/lifecycle/bundled-digests.js +4 -3
  42. package/dist/lifecycle/install.d.ts +7 -0
  43. package/dist/lifecycle/install.js +45 -3
  44. package/dist/lifecycle/manifest.d.ts +2 -2
  45. package/dist/lifecycle/manifest.js +5 -3
  46. package/dist/lifecycle/node.js +2 -0
  47. package/dist/lifecycle/persistent-state.js +1 -1
  48. package/dist/lifecycle/ports.d.ts +8 -0
  49. package/dist/lifecycle/seed-bundled.js +4 -1
  50. package/dist/manifest.d.ts +1 -1
  51. package/dist/manifest.js +3 -2
  52. package/fly/deploy-configs/fly.mjs +115 -0
  53. package/fly/deploy-ops/fly.mjs +102 -0
  54. package/fly/mcp.json +4 -0
  55. package/fly/plugin.json +81 -0
  56. package/fly/scripts/render-deploy-workflow.mjs +150 -0
  57. package/fly/skills/fly/SKILL.md +43 -0
  58. package/fly/skills/fly/references/fly-deploy.template.yml +117 -0
  59. package/fly/skills/fly/references/fly.template.toml +81 -0
  60. package/fly/tovu-credential-schemes.json +11 -0
  61. package/fly/tovu-deploy-configs.json +21 -0
  62. package/fly/tovu-deploy-ops.json +14 -0
  63. package/github/deploy-ops/github-actions.mjs +164 -0
  64. package/github/deployments/github.mjs +415 -0
  65. package/github/mcp.json +4 -0
  66. package/github/plugin.json +105 -0
  67. package/github/skills/github/SKILL.md +249 -0
  68. package/github/skills/github/references/actions.md +224 -0
  69. package/github/skills/github/references/auth-and-tokens.md +140 -0
  70. package/github/skills/github/references/repo-files.md +135 -0
  71. package/github/skills/github/references/site-backup.md +159 -0
  72. package/github/skills/github/references/source-control.md +123 -0
  73. package/github/source-control/backup-push.mjs +242 -0
  74. package/github/source-control/commit-site.mjs +459 -0
  75. package/github/source-control/github.mjs +93 -0
  76. package/github/source-control/write-files.mjs +468 -0
  77. package/github/tovu-deploy-ops.json +13 -0
  78. package/github/tovu-source-control.json +117 -0
  79. package/github-pages/NOTES.md +19 -0
  80. package/github-pages/mcp.json +4 -0
  81. package/github-pages/plugin.json +48 -0
  82. package/github-pages/skills/github-pages/SKILL.md +44 -0
  83. package/github-pages/targets/github-pages.mjs +543 -0
  84. package/github-pages/tovu-deploy-targets.json +327 -0
  85. package/google-login/exports/google-g.svg +1 -0
  86. package/google-login/exports/sign-in-button.css +68 -0
  87. package/google-login/exports/sign-in-button.html +4 -0
  88. package/google-login/mcp.json +4 -0
  89. package/google-login/plugin.json +59 -0
  90. package/google-login/skills/google-login/SKILL.md +96 -0
  91. package/google-login/tovu-login-providers.json +21 -0
  92. package/google-workspace/README.md +132 -0
  93. package/google-workspace/form-destinations/README.md +87 -0
  94. package/google-workspace/form-destinations/dedupe.mjs +43 -0
  95. package/google-workspace/form-destinations/delivery-result.mjs +69 -0
  96. package/google-workspace/form-destinations/mapping.mjs +152 -0
  97. package/google-workspace/form-destinations/sheets.mjs +202 -0
  98. package/google-workspace/lib/auth.mjs +81 -0
  99. package/google-workspace/lib/drive-files.mjs +54 -0
  100. package/google-workspace/lib/form-destination-settings.mjs +72 -0
  101. package/google-workspace/lib/http.mjs +168 -0
  102. package/google-workspace/lib/picker-flow.mjs +171 -0
  103. package/google-workspace/lib/picker-selection.mjs +107 -0
  104. package/google-workspace/lib/product-state.mjs +93 -0
  105. package/google-workspace/lib/request-policy.mjs +163 -0
  106. package/google-workspace/lib/sheets-a1.mjs +79 -0
  107. package/google-workspace/lib/sheets-admin-csv.mjs +82 -0
  108. package/google-workspace/lib/sheets-admin-request-kinds.mjs +183 -0
  109. package/google-workspace/lib/sheets-client.mjs +286 -0
  110. package/google-workspace/lib/surface-actions.mjs +107 -0
  111. package/google-workspace/mcp.json +4 -0
  112. package/google-workspace/plugin.json +77 -0
  113. package/google-workspace/products.json +12 -0
  114. package/google-workspace/skills/google-workspace/SKILL.md +82 -0
  115. package/google-workspace/skills/google-workspace/references/connection.md +102 -0
  116. package/google-workspace/skills/google-workspace/references/form-delivery.md +50 -0
  117. package/google-workspace/skills/google-workspace/references/sheets.md +149 -0
  118. package/google-workspace/skills/google-workspace/references/troubleshooting.md +31 -0
  119. package/google-workspace/tools/account-info.mjs +18 -0
  120. package/google-workspace/tools/sheets/README.md +54 -0
  121. package/google-workspace/tools/sheets/copy-tab.mjs +23 -0
  122. package/google-workspace/tools/sheets/copy.mjs +23 -0
  123. package/google-workspace/tools/sheets/create.mjs +35 -0
  124. package/google-workspace/tools/sheets/find-replace.mjs +38 -0
  125. package/google-workspace/tools/sheets/find.mjs +52 -0
  126. package/google-workspace/tools/sheets/get-by-data-filter.mjs +23 -0
  127. package/google-workspace/tools/sheets/get.mjs +22 -0
  128. package/google-workspace/tools/sheets/values-append.mjs +27 -0
  129. package/google-workspace/tools/sheets/values-batch-clear.mjs +20 -0
  130. package/google-workspace/tools/sheets/values-batch-get.mjs +20 -0
  131. package/google-workspace/tools/sheets/values-batch-update.mjs +29 -0
  132. package/google-workspace/tools/sheets/values-by-data-filter.mjs +62 -0
  133. package/google-workspace/tools/sheets/values-clear.mjs +20 -0
  134. package/google-workspace/tools/sheets/values-get.mjs +21 -0
  135. package/google-workspace/tools/sheets/values-update.mjs +26 -0
  136. package/google-workspace/tools/sheets-admin/README.md +45 -0
  137. package/google-workspace/tools/sheets-admin/basic-filter.mjs +32 -0
  138. package/google-workspace/tools/sheets-admin/batch-update.mjs +25 -0
  139. package/google-workspace/tools/sheets-admin/developer-metadata.mjs +28 -0
  140. package/google-workspace/tools/sheets-admin/export-csv.mjs +67 -0
  141. package/google-workspace/tools/sheets-admin/filter-views.mjs +38 -0
  142. package/google-workspace/tools/sheets-admin/format.mjs +70 -0
  143. package/google-workspace/tools/sheets-admin/named-ranges.mjs +42 -0
  144. package/google-workspace/tools/sheets-admin/open-existing.mjs +34 -0
  145. package/google-workspace/tools/sheets-admin/protected-ranges-get.mjs +18 -0
  146. package/google-workspace/tools/sheets-admin/sort.mjs +18 -0
  147. package/google-workspace/tools/sheets-admin/tab-modify.mjs +36 -0
  148. package/google-workspace/tovu-admin-surfaces.json +63 -0
  149. package/google-workspace/tovu-agent-tools.json +1178 -0
  150. package/google-workspace/tovu-connections.json +35 -0
  151. package/google-workspace/tovu-form-destinations.json +10 -0
  152. package/google-workspace/ui/README.md +73 -0
  153. package/google-workspace/ui/connection-panel.handler.mjs +167 -0
  154. package/google-workspace/ui/connection-panel.html +17 -0
  155. package/google-workspace/ui/connection-panel.mjs +189 -0
  156. package/google-workspace/ui/connection-view.mjs +120 -0
  157. package/google-workspace/ui/destination-panel.handler.mjs +106 -0
  158. package/google-workspace/ui/destination-panel.html +17 -0
  159. package/google-workspace/ui/destination-panel.mjs +150 -0
  160. package/google-workspace/ui/dom.mjs +140 -0
  161. package/google-workspace/ui/panel.css +170 -0
  162. package/google-workspace/ui/picker-flow.mjs +87 -0
  163. package/google-workspace/ui/picker-view.mjs +44 -0
  164. package/google-workspace/ui/picker.handler.mjs +56 -0
  165. package/google-workspace/ui/picker.html +17 -0
  166. package/google-workspace/ui/picker.mjs +88 -0
  167. package/google-workspace/ui/surface-client.mjs +227 -0
  168. package/higgsfield-media/mcp.json +9 -0
  169. package/higgsfield-media/plugin.json +109 -0
  170. package/higgsfield-media/skills/higgsfield-media/SKILL.md +325 -0
  171. package/higgsfield-media/skills/higgsfield-media/references/failure-modes.md +116 -0
  172. package/higgsfield-media/skills/higgsfield-media/references/models-and-plan-gates.md +142 -0
  173. package/jev/mcp.json +4 -0
  174. package/jev/plugin.json +44 -0
  175. package/jev/skills/jev/SKILL.md +308 -0
  176. package/jev/skills/jev/references/cookbooks.md +197 -0
  177. package/jev/skills/jev/references/jaggedness.md +135 -0
  178. package/jev/skills/jev/references/javascript-sdk.md +146 -0
  179. package/jev/skills/jev/references/patterns.md +123 -0
  180. package/jev/skills/jev/references/primitives-reference.md +187 -0
  181. package/langfuse/plugin.json +22 -0
  182. package/meilisearch/mcp.json +4 -0
  183. package/meilisearch/plugin.json +54 -0
  184. package/meilisearch/providers/meilisearch.mjs +651 -0
  185. package/meilisearch/skills/meilisearch/SKILL.md +43 -0
  186. package/meilisearch/tovu-search-providers.json +10 -0
  187. package/netlify/NOTES.md +13 -0
  188. package/netlify/mcp.json +4 -0
  189. package/netlify/plugin.json +47 -0
  190. package/netlify/skills/netlify/SKILL.md +39 -0
  191. package/netlify/targets/netlify.mjs +384 -0
  192. package/netlify/tovu-consent.json +6 -0
  193. package/netlify/tovu-deploy-targets.json +163 -0
  194. package/opentelemetry/mcp.json +4 -0
  195. package/opentelemetry/plugin.json +52 -0
  196. package/opentelemetry/skills/opentelemetry/SKILL.md +39 -0
  197. package/package.json +161 -6
  198. package/posthog/mcp.json +4 -0
  199. package/posthog/plugin.json +46 -0
  200. package/posthog/skills/posthog/SKILL.md +36 -0
  201. package/railway/deploy-configs/railway.mjs +135 -0
  202. package/railway/deploy-ops/railway.mjs +167 -0
  203. package/railway/mcp.json +4 -0
  204. package/railway/plugin.json +45 -0
  205. package/railway/skills/railway/SKILL.md +66 -0
  206. package/railway/tovu-deploy-configs.json +16 -0
  207. package/railway/tovu-deploy-ops.json +11 -0
  208. package/render/deploy-configs/render.mjs +141 -0
  209. package/render/deploy-ops/render.mjs +152 -0
  210. package/render/mcp.json +4 -0
  211. package/render/plugin.json +47 -0
  212. package/render/skills/render/SKILL.md +63 -0
  213. package/render/tovu-deploy-configs.json +16 -0
  214. package/render/tovu-deploy-ops.json +11 -0
  215. package/resend/NOTES.md +52 -0
  216. package/resend/adapters/resend.mjs +161 -0
  217. package/resend/lib/domains.mjs +137 -0
  218. package/resend/lib/errors.mjs +25 -0
  219. package/resend/lib/feedback.mjs +118 -0
  220. package/resend/mcp.json +13 -0
  221. package/resend/plugin.json +192 -0
  222. package/resend/skills/resend/SKILL.md +107 -0
  223. package/resend/tovu-mail-adapters.json +22 -0
  224. package/s3-compatible/NOTES.md +11 -0
  225. package/s3-compatible/mcp.json +4 -0
  226. package/s3-compatible/object-stores/s3-compatible.mjs +287 -0
  227. package/s3-compatible/plugin.json +56 -0
  228. package/s3-compatible/skills/s3-compatible/SKILL.md +39 -0
  229. package/s3-compatible/targets/s3-compatible.mjs +1054 -0
  230. package/s3-compatible/tovu-deploy-targets.json +432 -0
  231. package/s3-compatible/tovu-object-stores.json +29 -0
  232. package/sentry/mcp.json +4 -0
  233. package/sentry/plugin.json +49 -0
  234. package/sentry/skills/sentry/SKILL.md +34 -0
  235. package/shopify/mcp.json +4 -0
  236. package/shopify/plugin.json +22 -0
  237. package/shopify/skills/shopify/SKILL.md +103 -0
  238. package/shopify/skills/shopify/references/catalog-sync.md +74 -0
  239. package/shopify/skills/shopify/references/domains.md +26 -0
  240. package/shopify/skills/shopify/references/live-storefront.md +34 -0
  241. package/shopify/skills/shopify/references/storefront-snippets.md +21 -0
  242. package/site-compliance/mcp.json +4 -0
  243. package/site-compliance/plugin.json +34 -0
  244. package/site-compliance/skills/site-compliance/SKILL.md +220 -0
  245. package/site-compliance/skills/site-compliance/references/ccpa-cpra.md +115 -0
  246. package/site-compliance/skills/site-compliance/references/gdpr-eu-uk.md +143 -0
  247. package/site-compliance/skills/site-compliance/references/wcag-2-2.md +129 -0
  248. package/site-import/mcp.json +4 -0
  249. package/site-import/plugin.json +41 -0
  250. package/site-import/skills/site-import/SKILL.md +232 -0
  251. package/site-import/skills/site-import/references/content-mapping.md +166 -0
  252. package/site-import/skills/site-import/references/discovery-and-classification.md +117 -0
  253. package/site-import/skills/site-import/references/theme-extraction.md +34 -0
  254. package/slide-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
  255. package/slide-design/mcp.json +4 -0
  256. package/slide-design/plugin.json +26 -0
  257. package/slide-design/skills/slide-design/SKILL.md +103 -0
  258. package/slide-design/skills/slide-design/references/charts-and-exhibits.md +100 -0
  259. package/slide-design/skills/slide-design/references/html-deck.md +175 -0
  260. package/slide-design/skills/slide-design/references/layout-and-style.md +131 -0
  261. package/slide-design/skills/slide-design/references/review.md +93 -0
  262. package/slide-design/skills/slide-design/references/storyline.md +118 -0
  263. package/stripe/plugin.json +22 -0
  264. package/supabase/mcp.json +26 -0
  265. package/supabase/plugin.json +78 -0
  266. package/supabase/skills/supabase/SKILL.md +169 -0
  267. package/supabase/skills/supabase/references/failure-modes.md +101 -0
  268. package/ui-ux-design/CHANGELOG.md +28 -0
  269. package/ui-ux-design/LICENSE +201 -0
  270. package/ui-ux-design/NOTICE +14 -0
  271. package/ui-ux-design/README.md +62 -0
  272. package/ui-ux-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
  273. package/ui-ux-design/mcp.json +4 -0
  274. package/ui-ux-design/plugin.json +26 -0
  275. package/ui-ux-design/skills/frontend-accessibility/SKILL.md +60 -0
  276. package/ui-ux-design/skills/gstack-design/SKILL.md +53 -0
  277. package/ui-ux-design/skills/gstack-design/references/consultation.md +30 -0
  278. package/ui-ux-design/skills/gstack-design/references/html.md +29 -0
  279. package/ui-ux-design/skills/gstack-design/references/review.md +28 -0
  280. package/ui-ux-design/skills/gstack-design/references/shotgun.md +33 -0
  281. package/ui-ux-design/skills/gstack-design/references/upstream-notes.md +23 -0
  282. package/ui-ux-design/skills/interface-design/ORIGINAL.md +16 -0
  283. package/ui-ux-design/skills/interface-design/SKILL.md +41 -0
  284. package/ui-ux-design/skills/interface-design/references/commands/audit.md +40 -0
  285. package/ui-ux-design/skills/interface-design/references/commands/critique.md +21 -0
  286. package/ui-ux-design/skills/interface-design/references/commands/extract.md +34 -0
  287. package/ui-ux-design/skills/interface-design/references/commands/init.md +31 -0
  288. package/ui-ux-design/skills/interface-design/references/commands/status.md +22 -0
  289. package/ui-ux-design/skills/interface-design/references/examples/system-precision.md +29 -0
  290. package/ui-ux-design/skills/interface-design/references/examples/system-warmth.md +29 -0
  291. package/ui-ux-design/skills/interface-design/references/system-template.md +57 -0
  292. package/ui-ux-design/skills/shadcn-ui/README.md +244 -0
  293. package/ui-ux-design/skills/shadcn-ui/SKILL.md +333 -0
  294. package/ui-ux-design/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
  295. package/ui-ux-design/skills/shadcn-ui/examples/data-table.tsx +313 -0
  296. package/ui-ux-design/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
  297. package/ui-ux-design/skills/shadcn-ui/resources/component-catalog.md +481 -0
  298. package/ui-ux-design/skills/shadcn-ui/resources/customization-guide.md +516 -0
  299. package/ui-ux-design/skills/shadcn-ui/resources/migration-guide.md +463 -0
  300. package/ui-ux-design/skills/shadcn-ui/resources/setup-guide.md +412 -0
  301. package/ui-ux-design/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
  302. package/ui-ux-design/skills/ui-ux-design/SKILL.md +171 -0
  303. package/ui-ux-design/skills/ui-ux-design/references/brand-and-voice.md +78 -0
  304. package/ui-ux-design/skills/ui-ux-design/references/chrome-mobile-test-matrix.md +137 -0
  305. package/ui-ux-design/skills/ui-ux-design/references/components-and-states.md +112 -0
  306. package/ui-ux-design/skills/ui-ux-design/references/delight-and-motion.md +54 -0
  307. package/ui-ux-design/skills/ui-ux-design/references/foundations.md +145 -0
  308. package/ui-ux-design/skills/ui-ux-design/references/inclusive-ai-imagery.md +57 -0
  309. package/ui-ux-design/skills/ui-ux-design/references/kole-jain-uiux-concepts.md +104 -0
  310. package/ui-ux-design/skills/ui-ux-design/references/kuinetic-reference.md +184 -0
  311. package/ui-ux-design/skills/ui-ux-design/references/openai-frontend-skill.md +139 -0
  312. package/ui-ux-design/skills/ui-ux-design/references/premium-ui.md +55 -0
  313. package/ui-ux-design/skills/ui-ux-design/references/research-and-validation.md +88 -0
  314. package/ui-ux-design/skills/ui-ux-design/references/sam-crawford-premium-websites.md +103 -0
  315. package/ui-ux-design/skills/ui-ux-design/references/self-made-web-designer-core-skills.md +119 -0
  316. package/ui-ux-design/skills/ui-ux-design/references/ui-animation.md +173 -0
  317. package/ui-ux-design/skills/ui-ux-design/references/ui-loop.md +234 -0
  318. package/ui-ux-design/skills/ui-ux-design/references/visual-storytelling.md +54 -0
  319. package/ui-ux-design/skills/vercel-web-design-guidelines/SKILL.md +39 -0
  320. package/ui-ux-design/skills/web-compliance/SKILL.md +134 -0
  321. package/ui-ux-design/skills/web-compliance/references/audit-evidence.md +59 -0
  322. package/vercel/NOTES.md +23 -0
  323. package/vercel/mcp.json +4 -0
  324. package/vercel/object-stores/vercel-blob.mjs +147 -0
  325. package/vercel/plugin.json +47 -0
  326. package/vercel/skills/vercel/SKILL.md +48 -0
  327. package/vercel/targets/vercel.mjs +586 -0
  328. package/vercel/tovu-consent.json +6 -0
  329. package/vercel/tovu-deploy-targets.json +219 -0
  330. package/vercel/tovu-object-stores.json +10 -0
  331. package/wordpress/NOTES.md +33 -0
  332. package/wordpress/plugin.json +30 -0
  333. package/wordpress/skills/wordpress/SKILL.md +43 -0
  334. package/wordpress/skills/wordpress/references/free-site-redirect.md +46 -0
  335. package/wordpress/skills/wordpress/references/subdomain.md +66 -0
@@ -0,0 +1,586 @@
1
+ /**
2
+ * @file Vercel deploy target of the `vercel` Agent Plugin.
3
+ *
4
+ * Ported from `@jini-ai/devops` `src/deploy/vercel.ts` at Jini commit 28f67f9a: the body of
5
+ * {@link bindVercel} is that source with its TypeScript types stripped, same Vercel calls, error texts,
6
+ * redirect guard and Deployment Protection detector (`isVercelProtectedResponse`). Changes:
7
+ *
8
+ * - Network, timeouts, poll delay, reachability, naming, the redirect guard and `DeployError` come
9
+ * from the host kit the host injects (see {@link bindVercel}),
10
+ * bound once per target by {@link bindVercel}: an installed plugin has no npm dependencies.
11
+ * - `responseHeaders` (the host's security header set) is rendered into a root `vercel.json` here,
12
+ * which the first host (Tovu) used to add in its core.
13
+ * - Files are uploaded one per request to `POST /v2/files` (by SHA-1) and the deployment references
14
+ * them as `{ file, sha, size }`, the way Vercel's CLI does (2026-10-11). The devops source sent every
15
+ * file inline, base64, in the one `/v13/deployments` body, which Vercel refuses past 10 MB
16
+ * ("Request body too large"): any real site with photos.
17
+ *
18
+ * Plain JS by owner decision (2026-09-29).
19
+ */
20
+
21
+ import { createHash, randomInt } from "node:crypto";
22
+
23
+ export const VERCEL_TARGET_ID = "vercel";
24
+
25
+ /** `/v2/files` uploads in flight at once per publish: a few hundred files finish in reasonable time
26
+ * without tripping Vercel's rate limit. */
27
+ const VERCEL_UPLOAD_CONCURRENCY = 5;
28
+ /** Tries per file upload; a 429, a 5xx or a dropped connection is retried, anything else fails the
29
+ * publish at once. */
30
+ const VERCEL_UPLOAD_ATTEMPTS = 3;
31
+
32
+ /** The root file Vercel reads response-header rules from. */
33
+ const VERCEL_CONFIG_FILE = "vercel.json";
34
+
35
+ /**
36
+ * Renders a header set as a `vercel.json` with one catch-all header rule. Byte-identical to what
37
+ * the first host (Tovu) rendered in its core before this moved here.
38
+ *
39
+ * @param {Readonly<Record<string, string>>} headers
40
+ * @returns {string}
41
+ */
42
+ function renderVercelConfig(headers) {
43
+ const rule = { source: "/(.*)", headers: Object.entries(headers).map(([key, value]) => ({ key, value })) };
44
+ return `${JSON.stringify({ headers: [rule] }, null, 2)}\n`;
45
+ }
46
+
47
+ /**
48
+ * The file set actually deployed: `files`, plus a root `vercel.json` rendered from
49
+ * `responseHeaders`, which REPLACES a site-supplied one (the header set is the host's policy).
50
+ *
51
+ * @param {Array<{ file: string, data: string | Uint8Array, contentType?: string }>} files
52
+ * @param {Readonly<Record<string, string>> | undefined} responseHeaders
53
+ */
54
+ function withVercelConfigFile(files, responseHeaders) {
55
+ if (!responseHeaders || Object.keys(responseHeaders).length === 0) return files;
56
+ return [...files.filter((file) => file.file !== VERCEL_CONFIG_FILE), { file: VERCEL_CONFIG_FILE, data: renderVercelConfig(responseHeaders) }];
57
+ }
58
+
59
+ /**
60
+ * Content-addresses a file set for Vercel: the deployment's `files` entries (`{ file, sha, size }`,
61
+ * no data) and one upload body per distinct SHA-1, so identical files upload once.
62
+ *
63
+ * @param {Array<{ file: string, data: string | Uint8Array }>} files
64
+ * @returns {{ refs: Array<{ file: string, sha: string, size: number }>, blobs: Map<string, Buffer> }}
65
+ */
66
+ function vercelFileManifest(files) {
67
+ const blobs = new Map();
68
+ const refs = files.map((f) => {
69
+ const data = Buffer.from(f.data);
70
+ // Vercel requires SHA-1 in file references and x-vercel-digest upload headers.
71
+ // This is a wire-format requirement; substituting SHA-256 would break uploads.
72
+ const sha = createHash("sha1").update(data).digest("hex");
73
+ blobs.set(sha, data);
74
+ return { file: f.file, sha, size: data.length };
75
+ });
76
+ return { refs, blobs };
77
+ }
78
+
79
+ /**
80
+ * Binds the ported Vercel code to one host kit.
81
+ *
82
+ * @param {{ fetch(url: string, init: RequestInit, options: { timeoutMs: number }): Promise<Response>,
83
+ * timeouts: { QUICK: number, DEPLOY: number, UPLOAD: number }, sleep(ms: number): Promise<void> } & Record<string, any>} kit
84
+ * The host's deploy kit in the positional ABI installed modules are written against (devops'
85
+ * `DeployHostKit` helpers, called positionally; `DeployError` is `new (message, status?, details?)`).
86
+ * @returns The target class (constructed with `{ token, teamId?, teamSlug? }`, as in devops) and the
87
+ * protected-response detector.
88
+ */
89
+ export function bindVercel(kit) {
90
+ const { fetch: fetchWithTimeout, timeouts: FETCH_TIMEOUT_MS, sleep, DeployError, checkDeploymentUrl, normalizeDeploymentUrl, waitForReachableDeploymentUrl, safeProjectLabel, assertNotRedirected, redirectGuardInit } = kit;
91
+ const VERCEL_TARGET_ID = 'vercel';
92
+ const VERCEL_API = 'https://api.vercel.com';
93
+ const VERCEL_PROTECTED_MESSAGE = 'Deployment is protected by Vercel. Disable Deployment Protection or use a custom domain to make this link public.';
94
+ /**
95
+ * Heuristically detects Vercel's own Deployment Protection auth wall from a
96
+ * 401 response, so `checkReachability` can report `status: 'protected'`
97
+ * (an actionable, non-error outcome) instead of `reachable: false` with a
98
+ * generic message.
99
+ *
100
+ * @param resp - The HTTP response received from probing a deployment URL.
101
+ * @param body - The response body (only read for 401s by the caller).
102
+ * @returns `true` if the response looks like Vercel's SSO/auth gate.
103
+ * @complexity O(1) — a handful of regex tests over already-fetched strings.
104
+ * @overallScore 100/100
105
+ */
106
+ function isVercelProtectedResponse(resp, body = '') {
107
+ const { server, setCookie } = protectionHeaders({ resp });
108
+ const text = String(body || '');
109
+ if (/vercel/i.test(server) || /_vercel_sso_nonce/i.test(setCookie)) return true;
110
+ return /Authentication Required|Vercel Authentication|vercel\.com\/sso-api/i.test(text);
111
+ }
112
+ function protectionHeaders({ resp }, _options = {}) {
113
+ return { server: resp.headers?.get?.('server') || '', setCookie: resp.headers?.get?.('set-cookie') || '' };
114
+ }
115
+ /**
116
+ * `DeployTarget` adapter for Vercel's v13 deployments API. Publishes a
117
+ * caller-supplied file set as a single deployment, polls until Vercel
118
+ * reports a terminal `readyState`, then waits for the resulting URL to
119
+ * become publicly reachable.
120
+ *
121
+ * Genericized from the origin product's daemon deploy module's
122
+ * `deployToVercel`: the origin's product-specific deployment-name
123
+ * convention is replaced by `input.projectName` (sanitized the same way);
124
+ * token/team config is supplied by the caller instead of being read from a
125
+ * product-owned local config file — persistence of that config is now the
126
+ * caller's concern, not this package's.
127
+ */
128
+ class VercelDeployTarget {
129
+ config;
130
+ id = VERCEL_TARGET_ID;
131
+ resolvedProjectName;
132
+ resolvedPublicUrl;
133
+ constructor(config) {
134
+ this.config = config;
135
+ }
136
+ /** Resolve the host-assigned production domain before rendering. A first publish creates
137
+ * the same project publish() would create, then reads its actual alias (names may collide).
138
+ * @complexity At most three bounded API calls, at most 100 domain records; no file uploads.
139
+ * @throws DeployError for refused/ambiguous inventories; never fabricates a public URL.
140
+ */
141
+ async resolvePublicUrl({ projectName }, _options = {}) {
142
+ this.resolvedProjectName = safeVercelProjectName(projectName);
143
+ this.resolvedPublicUrl = await resolveVercelPublicUrl({ config: this.config, name: this.resolvedProjectName });
144
+ return this.resolvedPublicUrl;
145
+ }
146
+ /**
147
+ * Publishes `input.files` as a new Vercel deployment and waits for the
148
+ * resulting URL to be reachable.
149
+ *
150
+ * @param input - File set plus a caller-chosen project name label.
151
+ * @returns The published deployment's URL/status once Vercel has a
152
+ * terminal `readyState` (or the poll budget is exhausted).
153
+ * @throws {DeployError} If `config.token` is missing, or Vercel's API
154
+ * rejects the deployment request or reports `readyState: 'ERROR'`.
155
+ * @complexity O(files) to hash and upload them, plus a bounded poll loop
156
+ * (`pollVercelDeployment`) and the reachability wait's own bound.
157
+ * @overallScore 100/100
158
+ */
159
+ async publish(input) {
160
+ if (!this.config.token) {
161
+ throw new DeployError('Vercel token is required.', 400);
162
+ }
163
+ const { refs, blobs } = vercelFileManifest(withVercelConfigFile(input.files, input.responseHeaders));
164
+ await uploadVercelFiles(this.config, blobs, [...blobs.keys()]);
165
+ const name = this.resolvedProjectName ?? safeVercelProjectName(input.projectName);
166
+ const created = await createUploadedVercelDeployment({ config: this.config, name, refs, blobs });
167
+ const fallbackId = typeof created.uid === 'string' ? created.uid : undefined;
168
+ const deploymentId = typeof created.id === 'string' ? created.id : fallbackId;
169
+ const initialUrl = deploymentUrl(created);
170
+ const ready = await readyVercelDeployment({ config: this.config, deploymentId, created });
171
+ const link = await publicVercelLink({ name, ready, created, initialUrl, publicUrl: this.resolvedPublicUrl });
172
+ return {
173
+ targetId: this.id,
174
+ url: link.url || deploymentUrl(ready) || initialUrl,
175
+ ...(deploymentId ? { deploymentId } : {}),
176
+ status: link.status,
177
+ statusMessage: link.statusMessage,
178
+ ...(link.reachableAt !== undefined ? { reachableAt: link.reachableAt } : {}),
179
+ };
180
+ }
181
+ /** Probes `url`, recognizing Vercel's own Deployment Protection auth wall. */
182
+ async checkReachability(url) {
183
+ return checkDeploymentUrl(url, {
184
+ detectProtected: isVercelProtectedResponse,
185
+ protectedMessage: VERCEL_PROTECTED_MESSAGE,
186
+ });
187
+ }
188
+ }
189
+ /** Read the provider's production-only, verified, nonredirecting domains. Refuse a partial
190
+ * inventory rather than select a different canonical URL on different pages of the result.
191
+ * @complexity One bounded request; response limited to 100 records by the provider.
192
+ */
193
+ async function readPublicDomains({ config, name }, _options = {}) {
194
+ const url = new URL(`${VERCEL_API}/v9/projects/${encodeURIComponent(name)}/domains${vercelTeamQuery(config)}`);
195
+ for (const [key, value] of Object.entries({ production: 'true', redirects: 'false', verified: 'true', limit: '100' })) url.searchParams.set(key, value);
196
+ return fetchWithTimeout(url.href, redirectGuardInit({ method: 'GET', headers: { Authorization: `Bearer ${config.token}` } }), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
197
+ }
198
+ /** Provision only after an authoritative not-found response; permission/network failures never create.
199
+ * @complexity One POST with the same project name and framework as the existing publish request.
200
+ */
201
+ async function createPublicProject({ config, name }, _options = {}) {
202
+ const response = await fetchWithTimeout(`${VERCEL_API}/v11/projects${vercelTeamQuery(config)}`, redirectGuardInit({
203
+ method: 'POST', headers: { Authorization: `Bearer ${config.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, framework: null }),
204
+ }), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
205
+ assertNotRedirected(response, 'Vercel project creation');
206
+ const body = await readVercelJson(response);
207
+ if (!response.ok) throw vercelError(body, response.status);
208
+ }
209
+ /** Actual domain evidence, never a name-derived hostname; preparation stays inside this provider.
210
+ * @complexity At most three API requests and O(domains), capped at 100; errors propagate.
211
+ */
212
+ async function resolveVercelPublicUrl({ config, name }, _options = {}) {
213
+ if (!config.token) throw new DeployError('Vercel token is required.', 400);
214
+ let response = await readPublicDomains({ config, name });
215
+ if (response.status === 404) {
216
+ await response.body?.cancel();
217
+ await createPublicProject({ config, name });
218
+ response = await readPublicDomains({ config, name });
219
+ }
220
+ assertNotRedirected(response, 'Vercel public domain lookup');
221
+ const body = await readVercelJson(response);
222
+ if (!response.ok) throw vercelError(body, response.status);
223
+ return publicDomainUrl({ body, name });
224
+ }
225
+ /** Select a complete, bounded inventory. Missing evidence is a refusal, never a guessed alias.
226
+ * @complexity O(domains), capped at 100 records; pure, throws DeployError for missing evidence.
227
+ */
228
+ function publicDomainUrl({ body, name }, _options = {}) {
229
+ const entries = Array.isArray(body.domains) ? body.domains : [];
230
+ if (body.pagination?.next != null || entries.length > 100) throw new DeployError('Public domain inventory exceeds 100 domains; choose a smaller destination.', 400);
231
+ const domains = entries.filter(domain => domain.verified === true && !domain.redirect && !domain.gitBranch && typeof domain.name === 'string');
232
+ const domain = domains.find(domain => domain.name === `${name}.vercel.app`) ?? domains[0];
233
+ if (!domain) throw new DeployError('Vercel did not report a verified public domain for this project.', 400);
234
+ return `https://${domain.name}`;
235
+ }
236
+ async function createUploadedVercelDeployment({ config, name, refs, blobs }, _options = {}) {
237
+ // `target: "production"`: without it Vercel makes a preview deployment, and only a project's
238
+ // FIRST deployment takes the production alias, so every later publish never reached the
239
+ // public address (2026-10-11). A publish is meant to update the live site.
240
+ const body = JSON.stringify({ name, files: refs, target: 'production', projectSettings: { framework: null } });
241
+ let createResp = await createVercelDeployment(config, body);
242
+ let created = await readVercelJson(createResp);
243
+ const missing = created?.error?.code === 'missing_files' ? created.error.missing : undefined;
244
+ if (!createResp.ok && Array.isArray(missing)) {
245
+ // Vercel did not keep (or never got) some uploads: send exactly what it names, then create
246
+ // once more. A second `missing_files` is reported like any other create failure.
247
+ await uploadVercelFiles(config, blobs, missing.filter((sha) => blobs.has(sha)));
248
+ createResp = await createVercelDeployment(config, body);
249
+ created = await readVercelJson(createResp);
250
+ }
251
+ if (!createResp.ok)
252
+ throw vercelError(created, createResp.status);
253
+ return created;
254
+ }
255
+ async function readyVercelDeployment({ config, deploymentId, created }, _options = {}) {
256
+ const ready = deploymentId ? await pollVercelDeployment(config, deploymentId) : created;
257
+ if (ready?.readyState === 'ERROR') {
258
+ const readyError = ready?.error;
259
+ // `ready ?? undefined` — the `undefined` side is unreachable: this line
260
+ // only runs when `ready?.readyState === 'ERROR'` just matched, which
261
+ // requires `ready` itself to already be a real object (optional
262
+ // chaining on a null/undefined `ready` would make the whole comparison
263
+ // `undefined === 'ERROR'`, i.e. false, skipping this block entirely).
264
+ // So by the time `ready ?? undefined` evaluates, `ready` is always
265
+ // truthy and the `??` is a no-op at runtime. Kept (not simplified to
266
+ // just `ready`) purely for type-level reasons: `ready`'s static type is
267
+ // `JsonObject | null` (from `pollVercelDeployment`'s return type /
268
+ // `created`'s), and `DeployError`'s `details` parameter accepts
269
+ // `JsonObject | string | undefined` — not `null` — so this coalesce is
270
+ // a compile-time-required null-to-undefined conversion that TypeScript's
271
+ // control-flow analysis cannot narrow away just from the optional-chained
272
+ // comparison above. See packages/devops/source-map.md's 2026-07-22
273
+ // addition for the full re-derivation.
274
+ throw new DeployError(readyError?.message || 'Vercel deployment failed.', 502, ready ?? undefined);
275
+ }
276
+ return ready;
277
+ }
278
+ async function publicVercelLink({ name, ready, created, initialUrl, publicUrl }, _options = {}) {
279
+ const aliases = deploymentUrlCandidates(`${name}.vercel.app`, ready, created);
280
+ const candidates = publicUrl ? [publicUrl, ...aliases.filter(alias => alias !== publicUrl)] : aliases;
281
+ let link = await waitForReachableDeploymentUrl(candidates.length ? candidates : [initialUrl], {
282
+ providerLabel: 'Vercel',
283
+ detectProtected: isVercelProtectedResponse,
284
+ protectedMessage: VERCEL_PROTECTED_MESSAGE,
285
+ });
286
+ // The kit counts any 3xx as reachable, but Deployment Protection answers a per-deployment
287
+ // URL with a redirect to Vercel's login, which is not a public link.
288
+ if (link.status === 'ready' && await redirectsToVercelLogin(link.url)) {
289
+ link = { status: 'protected', url: link.url, statusMessage: VERCEL_PROTECTED_MESSAGE };
290
+ }
291
+ return link;
292
+ }
293
+ /** One HEAD, redirects not followed: does `url` send visitors to Vercel's SSO/login wall? Any
294
+ * failure answers `false`, leaving the reachability result as it was. */
295
+ async function redirectsToVercelLogin(url) {
296
+ try {
297
+ const resp = await fetchWithTimeout(url, { method: 'HEAD', redirect: 'manual' }, { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
298
+ await resp.arrayBuffer().catch(() => undefined);
299
+ return resp.status >= 300 && resp.status < 400 && /vercel\.com\/(sso-api|login)/i.test(resp.headers.get('location') || '');
300
+ }
301
+ catch {
302
+ return false;
303
+ }
304
+ }
305
+ async function createVercelDeployment(config, body) {
306
+ const resp = await fetchWithTimeout(`${VERCEL_API}/v13/deployments${vercelTeamQuery(config)}`, redirectGuardInit({
307
+ method: 'POST',
308
+ headers: {
309
+ Authorization: `Bearer ${config.token}`,
310
+ 'Content-Type': 'application/json',
311
+ },
312
+ body,
313
+ }), { timeoutMs: FETCH_TIMEOUT_MS.UPLOAD });
314
+ assertNotRedirected(resp, 'Vercel deployment creation');
315
+ return resp;
316
+ }
317
+ /**
318
+ * Uploads `shas` (keys of `blobs`) to `POST /v2/files`, {@link VERCEL_UPLOAD_CONCURRENCY} at a
319
+ * time. The first failure stops the remaining queue and rejects.
320
+ *
321
+ * @complexity O(n) requests, plus at most `VERCEL_UPLOAD_ATTEMPTS - 1` retries each.
322
+ */
323
+ async function uploadVercelFiles(config, blobs, shas) {
324
+ const queue = [...shas];
325
+ const worker = async () => {
326
+ while (queue.length) {
327
+ const sha = queue.shift();
328
+ try {
329
+ await uploadVercelFile(config, sha, blobs.get(sha));
330
+ }
331
+ catch (err) {
332
+ queue.length = 0;
333
+ throw err;
334
+ }
335
+ }
336
+ };
337
+ await Promise.all(Array.from({ length: Math.min(VERCEL_UPLOAD_CONCURRENCY, queue.length) }, worker));
338
+ }
339
+ function vercelUploadTransportError({ err }, _options = {}) {
340
+ // Node's fetch reports every transport failure as a bare "fetch failed"; the cause (a
341
+ // reset socket, a TLS or DNS error) is the only part that says what happened.
342
+ const cause = err?.cause?.message || err?.cause?.code;
343
+ const detail = cause ? ` (${cause})` : '';
344
+ return new DeployError(`Vercel file upload failed: ${err?.message ?? err}${detail}`, 502);
345
+ }
346
+ async function tryVercelFileUpload({ config, sha, data, attempt }, _options = {}) {
347
+ try {
348
+ return await fetchWithTimeout(`${VERCEL_API}/v2/files${vercelTeamQuery(config)}`, redirectGuardInit({
349
+ method: 'POST',
350
+ headers: {
351
+ Authorization: `Bearer ${config.token}`,
352
+ 'Content-Type': 'application/octet-stream',
353
+ 'Content-Length': String(data.length),
354
+ 'x-vercel-digest': sha,
355
+ },
356
+ body: data,
357
+ }), { timeoutMs: FETCH_TIMEOUT_MS.UPLOAD });
358
+ }
359
+ catch (err) {
360
+ // A dropped connection is retried like a 5xx (the first real plannie publish lost one of
361
+ // ~200 uploads that way); a timeout is not, since each try already waited the full bound.
362
+ if (attempt < VERCEL_UPLOAD_ATTEMPTS && !/timed out/i.test(String(err?.message))) {
363
+ await sleep(1000 * attempt);
364
+ return undefined;
365
+ }
366
+ throw vercelUploadTransportError({ err });
367
+ }
368
+ }
369
+ async function uploadVercelFile(config, sha, data) {
370
+ for (let attempt = 1; ; attempt += 1) {
371
+ const resp = await tryVercelFileUpload({ config, sha, data, attempt });
372
+ if (resp === undefined) continue;
373
+ assertNotRedirected(resp, 'Vercel file upload');
374
+ if (resp.ok) {
375
+ // Drain the small JSON ack so the connection goes back to the pool.
376
+ await resp.arrayBuffer().catch(() => undefined);
377
+ return;
378
+ }
379
+ if (attempt < VERCEL_UPLOAD_ATTEMPTS && (resp.status === 429 || resp.status >= 500)) {
380
+ await resp.arrayBuffer().catch(() => undefined);
381
+ await sleep(1000 * attempt);
382
+ } else {
383
+ throw vercelError(await readVercelJson(resp), resp.status);
384
+ }
385
+ }
386
+ }
387
+ /**
388
+ * Polls a just-created Vercel deployment until it reaches a terminal
389
+ * `readyState` (`READY`/`ERROR`) or the fixed 30-attempt budget is spent
390
+ * (~1s for the first 5 attempts, 2s thereafter — roughly a minute).
391
+ *
392
+ * A response this file cannot parse as JSON is treated as "keep polling,"
393
+ * not an error — same reasoning as `github-pages.ts`'s `pollGitHubPagesBuild`
394
+ * (fixed 2026-08-16 for the identical bug class): the deployment already
395
+ * exists server-side by the time this loop runs (the create call above
396
+ * already succeeded), so a transient/edge/empty poll response is exactly as
397
+ * uninformative as a stuck in-progress state, never a reason to report an
398
+ * already-created deployment as failed. Any other non-2xx response whose
399
+ * body DID parse still fails fast, unchanged.
400
+ *
401
+ * @complexity O(30) network round-trips, bounded and fixed regardless of input size.
402
+ * @overallScore 90/100
403
+ * @tradeoffs Fixed attempt/backoff constants (lifted verbatim from the OD
404
+ * origin) rather than caller-configurable ones — acceptable for now since
405
+ * `publish` has no caller-facing timeout knob yet either; flagged as a
406
+ * minor extensibility gap rather than a correctness issue.
407
+ */
408
+ async function pollVercelDeployment(config, id) {
409
+ let last = null;
410
+ for (let i = 0; i < 30; i += 1) {
411
+ await sleep(i < 5 ? 1000 : 2000);
412
+ const resp = await fetchWithTimeout(`${VERCEL_API}/v13/deployments/${encodeURIComponent(id)}${vercelTeamQuery(config)}`, redirectGuardInit({ headers: { Authorization: `Bearer ${config.token}` } }), { timeoutMs: FETCH_TIMEOUT_MS.DEPLOY });
413
+ assertNotRedirected(resp, 'Vercel deployment status poll');
414
+ let json;
415
+ try {
416
+ json = await readVercelJson(resp);
417
+ }
418
+ catch {
419
+ // Nothing usable to report yet — keep polling within the same fixed budget
420
+ // rather than turning an ambiguous mid-poll hiccup into a reported failure.
421
+ continue;
422
+ }
423
+ if (!resp.ok)
424
+ throw vercelError(json, resp.status);
425
+ last = json;
426
+ if (json.readyState === 'READY' || json.readyState === 'ERROR')
427
+ return json;
428
+ }
429
+ return last;
430
+ }
431
+ function vercelTeamQuery(config) {
432
+ const params = new URLSearchParams();
433
+ if (config.teamId)
434
+ params.set('teamId', config.teamId);
435
+ else if (config.teamSlug)
436
+ params.set('slug', config.teamSlug);
437
+ else {
438
+ // Personal deployments need no team query parameter.
439
+ }
440
+ const s = params.toString();
441
+ return s ? `?${s}` : '';
442
+ }
443
+ /**
444
+ * Derives a Vercel-safe project name from a caller-supplied label, falling
445
+ * back to a random id if the label sanitizes to nothing. Replaces the OD
446
+ * origin's hardcoded `od-${projectId}` convention.
447
+ */
448
+ function safeVercelProjectName(projectName) {
449
+ return safeProjectLabel(projectName, 80) || `deploy-${randomInt(36 ** 8).toString(36).padStart(8, '0')}`;
450
+ }
451
+ async function readVercelJson(resp) {
452
+ try {
453
+ return (await resp.json());
454
+ }
455
+ catch {
456
+ throw new DeployError('Vercel returned a non-JSON response.', resp.status || 502);
457
+ }
458
+ }
459
+ function vercelErrorField({ json, field }, _options = {}) {
460
+ return json?.error && typeof json.error === 'object' ? json.error[field] : undefined;
461
+ }
462
+ function vercelError(json, status) {
463
+ const code = vercelErrorField({ json, field: 'code' });
464
+ const message = vercelErrorField({ json, field: 'message' }) ||
465
+ json?.message ||
466
+ `Vercel request failed (${status}).`;
467
+ if (code === 'forbidden' || /permission/i.test(String(message))) {
468
+ return new DeployError("You don't have permission to create a project.", status, json);
469
+ }
470
+ return new DeployError(String(message), status, json);
471
+ }
472
+ function deploymentUrl(json) {
473
+ const url = json?.url || json?.alias?.[0] || '';
474
+ if (!url)
475
+ return '';
476
+ return /^https?:\/\//i.test(url) ? url : `https://${url}`;
477
+ }
478
+ function appendDeploymentAlias({ alias, urls }, _options = {}) {
479
+ if (typeof alias === 'string') {
480
+ urls.push(alias);
481
+ return;
482
+ }
483
+ if (alias && typeof alias === 'object') {
484
+ const a = alias;
485
+ if (typeof a.domain === 'string')
486
+ urls.push(a.domain);
487
+ else if (typeof a.url === 'string')
488
+ urls.push(a.url);
489
+ else {
490
+ // Objects without a domain or URL are not deployment aliases.
491
+ }
492
+ }
493
+ }
494
+ function appendDeploymentAliases({ json, urls }, _options = {}) {
495
+ for (const alias of json.alias ?? []) {
496
+ if (typeof alias === 'string')
497
+ urls.push(alias);
498
+ }
499
+ for (const alias of json.aliases ?? []) {
500
+ appendDeploymentAlias({ alias, urls });
501
+ }
502
+ }
503
+ /**
504
+ * `if (!json) continue` skips a response that is not there. Of this file's one call site,
505
+ * `deploymentUrlCandidates(publicHost, ready, created)` in `publish()`, `created` is always a parsed
506
+ * `JsonObject` (`readVercelJson` returns one or throws), but `ready` is `null` when every one of
507
+ * `pollVercelDeployment`'s 30 status reads was unparseable (each such read is skipped with
508
+ * `continue`, so `last` is never assigned) — then only `created` supplies candidates. Same guard
509
+ * `netlify.ts`'s own `netlifyUrlCandidates` keeps for the same reason.
510
+ */
511
+ function deploymentUrlCandidates(publicHost, ...responses) {
512
+ // Aliases (the production `<project>.vercel.app` among them) are tried before `url`: `url` is the
513
+ // per-deployment address, which Deployment Protection puts behind Vercel's login by default.
514
+ const urls = [];
515
+ const deploymentUrls = [];
516
+ for (const json of responses) {
517
+ if (!json)
518
+ continue;
519
+ if (typeof json.url === 'string')
520
+ deploymentUrls.push(json.url);
521
+ appendDeploymentAliases({ json, urls });
522
+ }
523
+ const candidates = [...new Set([...urls, ...deploymentUrls].map(normalizeDeploymentUrl).filter(Boolean))];
524
+ // The public `<project>.vercel.app` alias goes first when Vercel lists it. Vercel orders aliases
525
+ // its own way, so the team-scoped `<project>-<team>.vercel.app` (behind Deployment Protection)
526
+ // can come first, and the reachability wait stops at the first `protected` answer: a publish
527
+ // whose public alias served 200 was reported `protected` (2026-10-11).
528
+ const publicUrl = normalizeDeploymentUrl(publicHost);
529
+ return candidates.includes(publicUrl) ? [publicUrl, ...candidates.filter((url) => url !== publicUrl)] : candidates;
530
+ }
531
+
532
+ return { VercelDeployTarget, isVercelProtectedResponse };
533
+ }
534
+
535
+ /**
536
+ * Reads one credential-check response WITHOUT touching its body: 401/403 means the host refused the
537
+ * credential, any other failure says nothing about it (a 5xx, a rate limit).
538
+ *
539
+ * @param {Response} response
540
+ * @returns {{ ok: true } | { ok: false, reason: "rejected" | "unreachable", statusCode: number }}
541
+ */
542
+ function classifyCredentialResponse(response) {
543
+ if (response.ok) return { ok: true };
544
+ return { ok: false, reason: response.status === 401 || response.status === 403 ? "rejected" : "unreachable", statusCode: response.status };
545
+ }
546
+
547
+ /**
548
+ * The token's account `user.username` from a `GET /v2/user` body, or `undefined`. Reads no other field.
549
+ * @param {unknown} body
550
+ * @returns {string | undefined}
551
+ */
552
+ function readVercelUsername(body) {
553
+ const user = typeof body === "object" && body !== null ? /** @type {Record<string, unknown>} */ (body).user : undefined;
554
+ const username = typeof user === "object" && user !== null ? /** @type {Record<string, unknown>} */ (user).username : undefined;
555
+ return typeof username === "string" && username !== "" ? username : undefined;
556
+ }
557
+
558
+ /** The module contract a host's deploy-target registry loads (`DeployTargetModule`). The one config
559
+ * field is the optional Vercel `teamId`. */
560
+ export default {
561
+ /** @param {{ credential: { token: string }, config: { teamId?: string }, kit: any }} context */
562
+ create({ credential, config, kit }) {
563
+ const { VercelDeployTarget } = bindVercel(kit);
564
+ return new VercelDeployTarget({ token: credential.token, ...(typeof config.teamId === "string" ? { teamId: config.teamId } : {}) });
565
+ },
566
+ /**
567
+ * `GET /v2/user`: 401/403 for a bad token. Reads back only `user.username`, required on both body
568
+ * shapes Vercel's OpenAPI schema declares and public (`vercel.com/<username>`), never email,
569
+ * billing or `defaultTeamId`.
570
+ * @param {{ credential: { token: string }, kit: any }} context
571
+ */
572
+ async verifyCredential({ credential, kit }) {
573
+ const response = await kit.fetch("https://api.vercel.com/v2/user", { headers: { Authorization: `Bearer ${credential.token}` } }, { timeoutMs: kit.timeouts.QUICK });
574
+ const check = classifyCredentialResponse(response);
575
+ if (!check.ok) return check;
576
+ return { ok: true, accountLabel: readVercelUsername(await response.json().catch(() => undefined)) };
577
+ },
578
+ /** @param {{ teamId?: unknown }} config */
579
+ validateConfig(config) {
580
+ if (typeof config.teamId === "string" && config.teamId.trim() === "") return "teamId must not be blank when provided";
581
+ return null;
582
+ },
583
+ basePath() {
584
+ return undefined;
585
+ },
586
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "contributions": [
4
+ { "kind": "region-resolver", "id": "vercel", "header": "x-vercel-ip-country" }
5
+ ]
6
+ }