@omega.js/manager 0.1.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 (416) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/LICENSE +98 -0
  3. package/README.md +165 -0
  4. package/bin/omega +2 -0
  5. package/bin/omg +2 -0
  6. package/claude-plugin/.claude-plugin/plugin.json +11 -0
  7. package/claude-plugin/.mcp.json +9 -0
  8. package/claude-plugin/README.md +139 -0
  9. package/claude-plugin/hooks/gate/mark.sh +58 -0
  10. package/claude-plugin/hooks/gate/run.sh +97 -0
  11. package/claude-plugin/hooks/guard/run.sh +249 -0
  12. package/claude-plugin/hooks/hooks.json +63 -0
  13. package/claude-plugin/hooks/inject/run.sh +127 -0
  14. package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
  15. package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
  16. package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
  17. package/claude-plugin/hooks/quality/run.sh +132 -0
  18. package/claude-plugin/hooks/shape/run.sh +45 -0
  19. package/claude-plugin/mcp-router-launch.js +29 -0
  20. package/claude-plugin/skills/README.md +48 -0
  21. package/claude-plugin/skills/accessibility/SKILL.md +31 -0
  22. package/claude-plugin/skills/analytics/SKILL.md +30 -0
  23. package/claude-plugin/skills/backend/SKILL.md +25 -0
  24. package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
  25. package/claude-plugin/skills/browser/SKILL.md +64 -0
  26. package/claude-plugin/skills/client/SKILL.md +24 -0
  27. package/claude-plugin/skills/desktop/SKILL.md +25 -0
  28. package/claude-plugin/skills/extension/SKILL.md +25 -0
  29. package/claude-plugin/skills/main/SKILL.md +54 -0
  30. package/claude-plugin/skills/manager/SKILL.md +25 -0
  31. package/claude-plugin/skills/seo/SKILL.md +36 -0
  32. package/claude-plugin/skills/theme/SKILL.md +33 -0
  33. package/claude-plugin/skills/web/SKILL.md +29 -0
  34. package/dist/cli-run.js +34 -0
  35. package/dist/cli.js +29 -0
  36. package/dist/commands/build.js +17 -0
  37. package/dist/commands/clean.js +16 -0
  38. package/dist/commands/company.js +46 -0
  39. package/dist/commands/deploy.js +143 -0
  40. package/dist/commands/dev.js +437 -0
  41. package/dist/commands/devlog.js +25 -0
  42. package/dist/commands/help.js +48 -0
  43. package/dist/commands/manage.js +53 -0
  44. package/dist/commands/migrate.js +76 -0
  45. package/dist/commands/onboard.js +31 -0
  46. package/dist/commands/pipeline.js +319 -0
  47. package/dist/commands/test.js +380 -0
  48. package/dist/commands/update.js +128 -0
  49. package/dist/commands/version.js +8 -0
  50. package/dist/company-init.js +167 -0
  51. package/dist/company.js +283 -0
  52. package/dist/config.js +1359 -0
  53. package/dist/devlog/index.js +226 -0
  54. package/dist/devlog/lib/collect-commits.js +136 -0
  55. package/dist/devlog/lib/generate-post.js +201 -0
  56. package/dist/devlog/lib/ghostii.js +122 -0
  57. package/dist/devlog/lib/project-map.js +98 -0
  58. package/dist/devlog/lib/publish-website.js +88 -0
  59. package/dist/index.js +45 -0
  60. package/dist/lib/agents-md.js +201 -0
  61. package/dist/lib/analytics-secret.js +22 -0
  62. package/dist/lib/argv.js +21 -0
  63. package/dist/lib/auth-admin.js +120 -0
  64. package/dist/lib/automation-client.js +210 -0
  65. package/dist/lib/backend-marketing.js +49 -0
  66. package/dist/lib/brand.js +240 -0
  67. package/dist/lib/bundle-id.js +53 -0
  68. package/dist/lib/claude-settings.js +125 -0
  69. package/dist/lib/company-scaffold.js +213 -0
  70. package/dist/lib/company.js +227 -0
  71. package/dist/lib/config-flow.js +350 -0
  72. package/dist/lib/config-write.js +41 -0
  73. package/dist/lib/custom-target.js +75 -0
  74. package/dist/lib/domain-utils.js +19 -0
  75. package/dist/lib/duration.js +39 -0
  76. package/dist/lib/env-order.js +236 -0
  77. package/dist/lib/env-secret.js +78 -0
  78. package/dist/lib/firestore-rest.js +326 -0
  79. package/dist/lib/framework-bin.js +156 -0
  80. package/dist/lib/gitignore.js +63 -0
  81. package/dist/lib/google-auth.js +495 -0
  82. package/dist/lib/google-token.js +66 -0
  83. package/dist/lib/jwt.js +31 -0
  84. package/dist/lib/legacy-oauth.js +59 -0
  85. package/dist/lib/node-version.js +127 -0
  86. package/dist/lib/owner-plan.js +78 -0
  87. package/dist/lib/package-scripts.js +152 -0
  88. package/dist/lib/preflight.js +470 -0
  89. package/dist/lib/product-create.js +144 -0
  90. package/dist/lib/run-command.js +74 -0
  91. package/dist/lib/run-gates.js +70 -0
  92. package/dist/lib/run-output.js +42 -0
  93. package/dist/lib/run-summary.js +384 -0
  94. package/dist/lib/scaffold.js +427 -0
  95. package/dist/lib/service-input.js +207 -0
  96. package/dist/lib/service-runner.js +466 -0
  97. package/dist/lib/stale.js +24 -0
  98. package/dist/lib/target-selection.js +155 -0
  99. package/dist/lib/verb-fanout.js +128 -0
  100. package/dist/lib/verify-live.js +188 -0
  101. package/dist/manage.js +311 -0
  102. package/dist/omega-bin.js +7 -0
  103. package/dist/onboard.js +532 -0
  104. package/dist/services/account/ensure/users.js +259 -0
  105. package/dist/services/account/index.js +97 -0
  106. package/dist/services/account/lib/backend-client.js +107 -0
  107. package/dist/services/account/lib/password.js +36 -0
  108. package/dist/services/account/lib/resolve-password.js +100 -0
  109. package/dist/services/advertising/ensure/sites.js +101 -0
  110. package/dist/services/advertising/index.js +101 -0
  111. package/dist/services/advertising/lib/adsense-api.js +41 -0
  112. package/dist/services/ai/ensure/keys.js +21 -0
  113. package/dist/services/ai/index.js +36 -0
  114. package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
  115. package/dist/services/analytics/ensure/google-streams.js +256 -0
  116. package/dist/services/analytics/ensure/meta-pixel.js +22 -0
  117. package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
  118. package/dist/services/analytics/index.js +134 -0
  119. package/dist/services/analytics/lib/analytics-api.js +156 -0
  120. package/dist/services/analytics/lib/meta-api.js +83 -0
  121. package/dist/services/analytics/lib/pixel-account.js +78 -0
  122. package/dist/services/analytics/lib/pixel-provision.js +152 -0
  123. package/dist/services/analytics/lib/pixel-specs.js +57 -0
  124. package/dist/services/analytics/lib/pixel-token.js +88 -0
  125. package/dist/services/analytics/lib/property-flow.js +77 -0
  126. package/dist/services/analytics/lib/tiktok-api.js +157 -0
  127. package/dist/services/analytics/lib/tiktok-auth.js +153 -0
  128. package/dist/services/assets/ensure/logo-gen.js +68 -0
  129. package/dist/services/assets/index.js +109 -0
  130. package/dist/services/assets/lib/assets-config.js +168 -0
  131. package/dist/services/assets/lib/brandmark-api.js +160 -0
  132. package/dist/services/assets/lib/derived.js +113 -0
  133. package/dist/services/assets/lib/font-loader.js +67 -0
  134. package/dist/services/assets/lib/reconcile.js +122 -0
  135. package/dist/services/assets/lib/reset.js +118 -0
  136. package/dist/services/assets/lib/svg-logo-generator.js +154 -0
  137. package/dist/services/assets/lib/svg-to-black.js +32 -0
  138. package/dist/services/assets/write/favicons.js +82 -0
  139. package/dist/services/assets/write/icons.js +64 -0
  140. package/dist/services/assets/write/process.js +96 -0
  141. package/dist/services/assets/write/reconcile.js +30 -0
  142. package/dist/services/assets/write/social-icons.js +108 -0
  143. package/dist/services/assets/write/templates.js +345 -0
  144. package/dist/services/bookmark/ensure/sync.js +255 -0
  145. package/dist/services/bookmark/index.js +17 -0
  146. package/dist/services/campaigns/ensure/contact-person.js +40 -0
  147. package/dist/services/campaigns/ensure/custom-fields.js +84 -0
  148. package/dist/services/campaigns/ensure/domain-auth.js +92 -0
  149. package/dist/services/campaigns/ensure/event-webhook.js +89 -0
  150. package/dist/services/campaigns/ensure/link-branding.js +187 -0
  151. package/dist/services/campaigns/ensure/list.js +50 -0
  152. package/dist/services/campaigns/ensure/segments.js +109 -0
  153. package/dist/services/campaigns/ensure/sender-identity.js +153 -0
  154. package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
  155. package/dist/services/campaigns/index.js +79 -0
  156. package/dist/services/campaigns/lib/dns-sync.js +81 -0
  157. package/dist/services/campaigns/lib/segment-query.js +138 -0
  158. package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
  159. package/dist/services/captcha/ensure/site-key.js +83 -0
  160. package/dist/services/captcha/index.js +78 -0
  161. package/dist/services/captcha/lib/console-url.js +45 -0
  162. package/dist/services/captcha/lib/recaptcha-api.js +36 -0
  163. package/dist/services/certificates/ensure/api-key.js +22 -0
  164. package/dist/services/certificates/ensure/bundle-ids.js +115 -0
  165. package/dist/services/certificates/ensure/certificates.js +187 -0
  166. package/dist/services/certificates/ensure/profiles.js +143 -0
  167. package/dist/services/certificates/index.js +225 -0
  168. package/dist/services/certificates/lib/apple-api.js +183 -0
  169. package/dist/services/certificates/lib/certificate-manager.js +252 -0
  170. package/dist/services/certificates/lib/identifier-manager.js +107 -0
  171. package/dist/services/certificates/lib/keychain.js +135 -0
  172. package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
  173. package/dist/services/certificates/lib/profile-manager.js +115 -0
  174. package/dist/services/chat/data/baseline-knowledge.md +96 -0
  175. package/dist/services/chat/ensure/chat.js +106 -0
  176. package/dist/services/chat/ensure/user.js +45 -0
  177. package/dist/services/chat/index.js +139 -0
  178. package/dist/services/chat/lib/baseline-knowledge.js +137 -0
  179. package/dist/services/cloud/ensure/authentication.js +267 -0
  180. package/dist/services/cloud/ensure/billing.js +120 -0
  181. package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
  182. package/dist/services/cloud/ensure/database.js +60 -0
  183. package/dist/services/cloud/ensure/firestore.js +79 -0
  184. package/dist/services/cloud/ensure/functions.js +49 -0
  185. package/dist/services/cloud/ensure/hosting.js +398 -0
  186. package/dist/services/cloud/ensure/oauth-consent.js +248 -0
  187. package/dist/services/cloud/ensure/project-settings.js +72 -0
  188. package/dist/services/cloud/ensure/sdk-config.js +95 -0
  189. package/dist/services/cloud/ensure/service-account.js +152 -0
  190. package/dist/services/cloud/ensure/services.js +103 -0
  191. package/dist/services/cloud/ensure/storage.js +47 -0
  192. package/dist/services/cloud/index.js +130 -0
  193. package/dist/services/cloud/lib/access-heal.js +130 -0
  194. package/dist/services/cloud/lib/firebase-api.js +662 -0
  195. package/dist/services/cloud/lib/project-flow.js +102 -0
  196. package/dist/services/directory/ensure/entry.js +48 -0
  197. package/dist/services/directory/index.js +64 -0
  198. package/dist/services/directory/lib/blocks.js +104 -0
  199. package/dist/services/disperse/index.js +45 -0
  200. package/dist/services/disperse/write/certs.js +117 -0
  201. package/dist/services/domain/ensure/nameservers.js +179 -0
  202. package/dist/services/domain/index.js +69 -0
  203. package/dist/services/domain/lib/namecheap-api.js +167 -0
  204. package/dist/services/domain/lib/registrars.js +46 -0
  205. package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
  206. package/dist/services/edge/ensure/cache-rules.js +105 -0
  207. package/dist/services/edge/ensure/dns-records.js +304 -0
  208. package/dist/services/edge/ensure/email-routing.js +292 -0
  209. package/dist/services/edge/ensure/rules-configuration.js +98 -0
  210. package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
  211. package/dist/services/edge/ensure/rules-redirect.js +107 -0
  212. package/dist/services/edge/ensure/rules-response-headers.js +90 -0
  213. package/dist/services/edge/ensure/rules-security.js +142 -0
  214. package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
  215. package/dist/services/edge/ensure/workers.js +219 -0
  216. package/dist/services/edge/ensure/zone-settings.js +108 -0
  217. package/dist/services/edge/ensure/zone.js +183 -0
  218. package/dist/services/edge/index.js +73 -0
  219. package/dist/services/edge/lib/cloudflare-api.js +84 -0
  220. package/dist/services/edge/lib/dns-records-helpers.js +518 -0
  221. package/dist/services/edge/lib/read-cache.js +16 -0
  222. package/dist/services/edge/lib/ruleset-helper.js +108 -0
  223. package/dist/services/edge/workers/omega-api-proxy.js +43 -0
  224. package/dist/services/email/data/baseline-filter.md +4 -0
  225. package/dist/services/email/data/baseline-knowledge.md +86 -0
  226. package/dist/services/email/ensure/agent.js +107 -0
  227. package/dist/services/email/ensure/user.js +45 -0
  228. package/dist/services/email/index.js +138 -0
  229. package/dist/services/email/lib/baseline.js +82 -0
  230. package/dist/services/email/lib/knowledge-file.js +53 -0
  231. package/dist/services/forms/ensure/form.js +46 -0
  232. package/dist/services/forms/ensure/user.js +45 -0
  233. package/dist/services/forms/index.js +138 -0
  234. package/dist/services/migrations/ensure/notifications.js +189 -0
  235. package/dist/services/migrations/ensure/orders.js +34 -0
  236. package/dist/services/migrations/ensure/payment-provider.js +132 -0
  237. package/dist/services/migrations/ensure/payments-intents.js +34 -0
  238. package/dist/services/migrations/ensure/state-retirement.js +266 -0
  239. package/dist/services/migrations/ensure/targets-rename.js +124 -0
  240. package/dist/services/migrations/ensure/users.js +1176 -0
  241. package/dist/services/migrations/index.js +101 -0
  242. package/dist/services/migrations/lib/attribution-touch.js +64 -0
  243. package/dist/services/migrations/lib/ensure-metadata.js +144 -0
  244. package/dist/services/migrations/lib/migration-runner.js +473 -0
  245. package/dist/services/migrations/lib/sanitize-strings.js +112 -0
  246. package/dist/services/migrations/lib/schema-validator.js +154 -0
  247. package/dist/services/monitoring/ensure/dsn.js +54 -0
  248. package/dist/services/monitoring/ensure/projects.js +135 -0
  249. package/dist/services/monitoring/index.js +54 -0
  250. package/dist/services/monitoring/lib/sentry-api.js +94 -0
  251. package/dist/services/newsletter/ensure/custom-fields.js +90 -0
  252. package/dist/services/newsletter/ensure/publication.js +94 -0
  253. package/dist/services/newsletter/ensure/segments.js +140 -0
  254. package/dist/services/newsletter/ensure/webhook.js +108 -0
  255. package/dist/services/newsletter/index.js +64 -0
  256. package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
  257. package/dist/services/newsletter/lib/segment-automation.js +577 -0
  258. package/dist/services/payment/ensure/chargebee-account.js +30 -0
  259. package/dist/services/payment/ensure/chargebee-products.js +359 -0
  260. package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
  261. package/dist/services/payment/ensure/paypal-account.js +36 -0
  262. package/dist/services/payment/ensure/paypal-products.js +374 -0
  263. package/dist/services/payment/ensure/paypal-webhook.js +128 -0
  264. package/dist/services/payment/ensure/stripe-account.js +93 -0
  265. package/dist/services/payment/ensure/stripe-disputes.js +56 -0
  266. package/dist/services/payment/ensure/stripe-products.js +245 -0
  267. package/dist/services/payment/ensure/stripe-radar.js +74 -0
  268. package/dist/services/payment/ensure/stripe-webhook.js +130 -0
  269. package/dist/services/payment/index.js +163 -0
  270. package/dist/services/payment/lib/chargebee-api.js +338 -0
  271. package/dist/services/payment/lib/payment-utils.js +116 -0
  272. package/dist/services/payment/lib/paypal-api.js +354 -0
  273. package/dist/services/payment/lib/provider-setup.js +134 -0
  274. package/dist/services/payment/lib/stripe-api.js +152 -0
  275. package/dist/services/repo/ensure/org.js +70 -0
  276. package/dist/services/repo/ensure/pages.js +72 -0
  277. package/dist/services/repo/ensure/repo.js +90 -0
  278. package/dist/services/repo/index.js +52 -0
  279. package/dist/services/repo/lib/github-api.js +196 -0
  280. package/dist/services/search/ensure/ga-link.js +57 -0
  281. package/dist/services/search/ensure/property.js +137 -0
  282. package/dist/services/search/ensure/sitemaps.js +70 -0
  283. package/dist/services/search/index.js +61 -0
  284. package/dist/services/search/lib/search-console-api.js +92 -0
  285. package/dist/services/seo/ensure/github-repos.js +271 -0
  286. package/dist/services/seo/index.js +55 -0
  287. package/dist/services/seo/lib/gh-api.js +161 -0
  288. package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
  289. package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
  290. package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
  291. package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
  292. package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
  293. package/dist/services/seo/templates/index.js +57 -0
  294. package/dist/services/server/ensure/brands.js +63 -0
  295. package/dist/services/server/index.js +42 -0
  296. package/dist/services/testing/ensure/target-checks.js +97 -0
  297. package/dist/services/testing/index.js +11 -0
  298. package/dist/services/testing/lib/checks.js +467 -0
  299. package/dist/services/update/index.js +20 -0
  300. package/dist/services/update/lib/cache.js +59 -0
  301. package/dist/services/update/lib/fingerprint.js +157 -0
  302. package/dist/services/update/write/targets.js +206 -0
  303. package/dist/services/workspace/ensure/agents.js +42 -0
  304. package/dist/services/workspace/ensure/claude-settings.js +34 -0
  305. package/dist/services/workspace/ensure/config.js +54 -0
  306. package/dist/services/workspace/ensure/defaults.js +60 -0
  307. package/dist/services/workspace/ensure/env-keys.js +55 -0
  308. package/dist/services/workspace/ensure/env-order.js +66 -0
  309. package/dist/services/workspace/ensure/env-rules.js +97 -0
  310. package/dist/services/workspace/ensure/gitignore.js +18 -0
  311. package/dist/services/workspace/ensure/scripts.js +126 -0
  312. package/dist/services/workspace/ensure/structure.js +97 -0
  313. package/dist/services/workspace/ensure/translation-sdk.js +148 -0
  314. package/dist/services/workspace/ensure/workflows.js +54 -0
  315. package/dist/services/workspace/index.js +8 -0
  316. package/dist/vendor/account/engine.js +182 -0
  317. package/dist/vendor/account/features.js +220 -0
  318. package/dist/vendor/account/index.js +53 -0
  319. package/dist/vendor/account/schema.js +272 -0
  320. package/dist/vendor/account/subscription.js +38 -0
  321. package/dist/vendor/config/company.js +31 -0
  322. package/dist/vendor/config/defaults.js +173 -0
  323. package/dist/vendor/config/demo.js +18 -0
  324. package/dist/vendor/config/desktop-artifacts.js +110 -0
  325. package/dist/vendor/config/edit.js +769 -0
  326. package/dist/vendor/config/env-delivery.js +145 -0
  327. package/dist/vendor/config/env-rules.js +93 -0
  328. package/dist/vendor/config/env-schema.js +1078 -0
  329. package/dist/vendor/config/env.js +445 -0
  330. package/dist/vendor/config/hooks.js +97 -0
  331. package/dist/vendor/config/index.js +237 -0
  332. package/dist/vendor/config/instances.js +208 -0
  333. package/dist/vendor/config/load.js +490 -0
  334. package/dist/vendor/config/merge.js +68 -0
  335. package/dist/vendor/config/order.js +139 -0
  336. package/dist/vendor/config/ports.js +374 -0
  337. package/dist/vendor/config/providers.js +32 -0
  338. package/dist/vendor/config/repo.js +142 -0
  339. package/dist/vendor/config/retired-keys.js +430 -0
  340. package/dist/vendor/config/schema.js +1610 -0
  341. package/dist/vendor/config/secrets.js +50 -0
  342. package/dist/vendor/config/seed.js +34 -0
  343. package/dist/vendor/config/site-global.js +205 -0
  344. package/dist/vendor/config/validate.js +554 -0
  345. package/dist/vendor/config/winback.js +61 -0
  346. package/dist/vendor/devkit/attach-log-file.js +262 -0
  347. package/dist/vendor/devkit/certs.js +199 -0
  348. package/dist/vendor/devkit/ci-workflows.js +520 -0
  349. package/dist/vendor/devkit/cli-router.js +156 -0
  350. package/dist/vendor/devkit/command-path.js +46 -0
  351. package/dist/vendor/devkit/deploy-record.js +180 -0
  352. package/dist/vendor/devkit/flows.js +317 -0
  353. package/dist/vendor/devkit/local-https.js +360 -0
  354. package/dist/vendor/devkit/local.js +1905 -0
  355. package/dist/vendor/devkit/logger.js +128 -0
  356. package/dist/vendor/devkit/omega-bin.js +345 -0
  357. package/dist/vendor/devkit/prompt.js +187 -0
  358. package/dist/vendor/devkit/safe-install.js +18 -0
  359. package/dist/vendor/devkit/stop-signals.js +28 -0
  360. package/dist/vendor/devkit/test/scope.js +162 -0
  361. package/dist/vendor/devkit/translate/cache.js +84 -0
  362. package/dist/vendor/devkit/translate/engine.js +243 -0
  363. package/dist/vendor/devkit/translate/index.js +49 -0
  364. package/dist/vendor/devkit/translate/languages.js +134 -0
  365. package/dist/vendor/devkit/translate/providers.js +188 -0
  366. package/dist/vendor/devkit/update.js +569 -0
  367. package/docs/AGENTS.md +200 -0
  368. package/docs/account.md +45 -0
  369. package/docs/advertising.md +46 -0
  370. package/docs/ai.md +34 -0
  371. package/docs/analytics.md +69 -0
  372. package/docs/assets.md +57 -0
  373. package/docs/bookmark.md +26 -0
  374. package/docs/brand.md +151 -0
  375. package/docs/campaigns.md +96 -0
  376. package/docs/captcha.md +46 -0
  377. package/docs/certificates.md +68 -0
  378. package/docs/chat.md +47 -0
  379. package/docs/cloud.md +110 -0
  380. package/docs/company.md +64 -0
  381. package/docs/directory.md +140 -0
  382. package/docs/disperse.md +46 -0
  383. package/docs/domain.md +56 -0
  384. package/docs/edge.md +234 -0
  385. package/docs/email.md +42 -0
  386. package/docs/forms.md +48 -0
  387. package/docs/index.md +109 -0
  388. package/docs/migration.md +203 -0
  389. package/docs/migrations.md +56 -0
  390. package/docs/monitoring.md +41 -0
  391. package/docs/newsletter.md +48 -0
  392. package/docs/payment.md +83 -0
  393. package/docs/repo.md +48 -0
  394. package/docs/search.md +44 -0
  395. package/docs/seo.md +34 -0
  396. package/docs/server.md +37 -0
  397. package/docs/shared/agent-docs.md +89 -0
  398. package/docs/shared/analytics.md +612 -0
  399. package/docs/shared/brands.md +51 -0
  400. package/docs/shared/breaking-changes.md +497 -0
  401. package/docs/shared/config.md +1387 -0
  402. package/docs/shared/deploys.md +215 -0
  403. package/docs/shared/icons.md +201 -0
  404. package/docs/shared/local-dev.md +147 -0
  405. package/docs/shared/logging.md +202 -0
  406. package/docs/shared/monitoring.md +153 -0
  407. package/docs/shared/publishing.md +183 -0
  408. package/docs/shared/rulings.md +34 -0
  409. package/docs/shared/testing.md +147 -0
  410. package/docs/shared/theming.md +604 -0
  411. package/docs/shared/translation.md +291 -0
  412. package/docs/shared/updates.md +61 -0
  413. package/docs/testing.md +34 -0
  414. package/docs/update.md +36 -0
  415. package/docs/workspace.md +64 -0
  416. package/package.json +88 -0
@@ -0,0 +1,662 @@
1
+ /**
2
+ * Firebase / Google Cloud API client — omega-manager's FirebaseAPI ported.
3
+ * One authenticated fetch wrapper (Google OAuth2) fronting the Firebase
4
+ * Management, Service Usage, Cloud Resource Manager, IAM, Billing, IAP,
5
+ * Identity Toolkit, Firestore, Realtime Database, Storage, and Hosting APIs.
6
+ *
7
+ * Every call handlers make is a named method here (nothing reaches into the
8
+ * auth layer directly) — tests fake this surface method-for-method.
9
+ *
10
+ * Auth: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET in the brand .env; tokens
11
+ * cache to the brand's .omega/auth/google-tokens.json.
12
+ */
13
+ const { GoogleOAuth2Client, GOOGLE_SCOPES } = require('../../../lib/google-auth.js');
14
+
15
+ const FIREBASE_API_BASE = 'https://firebase.googleapis.com/v1beta1';
16
+ const SERVICE_USAGE_API_BASE = 'https://serviceusage.googleapis.com/v1';
17
+ const IDENTITY_TOOLKIT_API_BASE = 'https://identitytoolkit.googleapis.com/v2';
18
+ const FIREBASE_HOSTING_API_BASE = 'https://firebasehosting.googleapis.com/v1beta1';
19
+ const RESOURCE_MANAGER_V1 = 'https://cloudresourcemanager.googleapis.com/v1';
20
+ const RESOURCE_MANAGER_V3 = 'https://cloudresourcemanager.googleapis.com/v3';
21
+ const FIRESTORE_API_BASE = 'https://firestore.googleapis.com/v1';
22
+ const CLOUD_BILLING_API_BASE = 'https://cloudbilling.googleapis.com/v1';
23
+
24
+ // Poll budget for a hosting domain claim (see settleDomainClaim)
25
+ const DOMAIN_CLAIM_POLL = { maxAttempts: 3, delayMs: 1000, pendingOk: true };
26
+
27
+ class FirebaseAPI {
28
+ constructor(options = {}) {
29
+ // GOOGLE_SCOPES (the manager-wide union) — one consent covers every
30
+ // Google service; see google-auth.js
31
+ this.auth = new GoogleOAuth2Client({
32
+ clientId: options.clientId || process.env.GOOGLE_CLIENT_ID,
33
+ clientSecret: options.clientSecret || process.env.GOOGLE_CLIENT_SECRET,
34
+ tokenStorePath: options.tokenStorePath || null,
35
+ scopes: GOOGLE_SCOPES,
36
+ });
37
+ }
38
+
39
+ request(url, options = {}) {
40
+ return this.auth.makeRequest(url, options);
41
+ }
42
+
43
+ /**
44
+ * Poll a long-running operation until it completes.
45
+ *
46
+ * A spent poll budget is not proof of failure (#56): some Google operations
47
+ * stay RUNNING for as long as the outside world takes to catch up, and a
48
+ * hosting domain claim waiting on DNS verification is the normal case, not
49
+ * an error. Callers whose operation can legitimately outlive the budget
50
+ * pass `pendingOk` and get `{ pending: true, operationName }` back; every
51
+ * other caller keeps the hard timeout. An operation that reports an error
52
+ * is terminal either way.
53
+ */
54
+ async waitForOperation(operationName, apiBase, options = {}) {
55
+ const maxAttempts = options.maxAttempts || 30;
56
+ const delayMs = options.delayMs || 2000;
57
+
58
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
59
+ const operation = await this.request(`${apiBase}/${operationName}`);
60
+
61
+ if (operation.done) {
62
+ if (operation.error) {
63
+ throw new Error(`Operation failed: ${operation.error.message}`);
64
+ }
65
+ return operation.response;
66
+ }
67
+
68
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
69
+ }
70
+
71
+ if (options.pendingOk) {
72
+ return { pending: true, operationName };
73
+ }
74
+
75
+ throw new Error('Operation timed out');
76
+ }
77
+
78
+ // ===========================================================================
79
+ // FIREBASE MANAGEMENT — projects + web apps
80
+ // ===========================================================================
81
+
82
+ /** Firebase project details (null if inaccessible) */
83
+ async getProject(projectId) {
84
+ try {
85
+ return await this.request(`${FIREBASE_API_BASE}/projects/${projectId}`);
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Raw project-access probe — getProject() swallows failures into null
93
+ * (its consumers treat "inaccessible" and "absent" alike), but the access
94
+ * self-heal needs the REAL failure: PERMISSION_DENIED here is the
95
+ * identity seam (lib/access-heal.js); anything else is a genuine fault.
96
+ * Resource Manager GET works for any role and for projects not yet
97
+ * Firebase-added.
98
+ */
99
+ probeProjectAccess(projectId) {
100
+ return this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}`);
101
+ }
102
+
103
+ /** All Firebase projects visible to the authed user */
104
+ async listProjects() {
105
+ const response = await this.request(`${FIREBASE_API_BASE}/projects`);
106
+ return response.results || [];
107
+ }
108
+
109
+ /**
110
+ * Organizations visible to the authed user — [] when there are none OR the
111
+ * search fails (org-less personal accounts are the normal case; a listing
112
+ * error must never block project creation).
113
+ */
114
+ async listOrganizations() {
115
+ try {
116
+ const response = await this.request(`${RESOURCE_MANAGER_V1}/organizations:search`, {
117
+ method: 'POST',
118
+ body: JSON.stringify({}),
119
+ });
120
+ return response.organizations || [];
121
+ } catch {
122
+ return [];
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Create a GCP project (inside the organization when configured — that's
128
+ * what gives the compute service account its default roles) and add
129
+ * Firebase to it. Both are long-running operations, awaited.
130
+ */
131
+ async createProject(projectId, displayName, organizationId = null) {
132
+ const payload = { projectId, name: displayName };
133
+ if (organizationId) {
134
+ payload.parent = { type: 'organization', id: organizationId };
135
+ }
136
+
137
+ const gcpResponse = await this.request(`${RESOURCE_MANAGER_V1}/projects`, {
138
+ method: 'POST',
139
+ body: JSON.stringify(payload),
140
+ });
141
+ if (gcpResponse.name) {
142
+ await this.waitForOperation(gcpResponse.name, RESOURCE_MANAGER_V1);
143
+ }
144
+
145
+ const firebaseResponse = await this.request(`${FIREBASE_API_BASE}/projects/${projectId}:addFirebase`, {
146
+ method: 'POST',
147
+ });
148
+ if (firebaseResponse.name) {
149
+ await this.waitForOperation(firebaseResponse.name, FIREBASE_API_BASE);
150
+ }
151
+
152
+ return { projectId, displayName };
153
+ }
154
+
155
+ async listWebApps(projectId) {
156
+ const response = await this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps`);
157
+ return response.apps || [];
158
+ }
159
+
160
+ /** Create a web app; waits for the operation and returns the appId */
161
+ async createWebApp(projectId, displayName) {
162
+ const response = await this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps`, {
163
+ method: 'POST',
164
+ body: JSON.stringify({ displayName }),
165
+ });
166
+
167
+ if (response.name) {
168
+ const result = await this.waitForOperation(response.name, FIREBASE_API_BASE);
169
+ return result?.appId;
170
+ }
171
+
172
+ return response.appId;
173
+ }
174
+
175
+ async updateWebAppDisplayName(projectId, appId, displayName) {
176
+ return this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps/${appId}?updateMask=displayName`, {
177
+ method: 'PATCH',
178
+ body: JSON.stringify({ displayName }),
179
+ });
180
+ }
181
+
182
+ /** SDK config for an existing web app */
183
+ async getWebAppConfig(projectId, appId) {
184
+ return this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps/${appId}/config`);
185
+ }
186
+
187
+ // ===========================================================================
188
+ // CLOUD RESOURCE MANAGER — GCP project identity + IAM policy
189
+ // ===========================================================================
190
+
191
+ /** GCP project (v3 — carries the editable displayName) */
192
+ async getGcpProject(projectId) {
193
+ try {
194
+ return await this.request(`${RESOURCE_MANAGER_V3}/projects/${projectId}`);
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+
200
+ async updateProjectName(projectId, displayName) {
201
+ return this.request(`${RESOURCE_MANAGER_V3}/projects/${projectId}?updateMask=displayName`, {
202
+ method: 'PATCH',
203
+ body: JSON.stringify({ displayName }),
204
+ });
205
+ }
206
+
207
+ /** Project number (required by the Service Usage API) */
208
+ async getProjectNumber(projectId) {
209
+ if (this._projectNumbers?.[projectId]) {
210
+ return this._projectNumbers[projectId];
211
+ }
212
+ const response = await this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}`);
213
+ this._projectNumbers = this._projectNumbers || {};
214
+ this._projectNumbers[projectId] = response.projectNumber;
215
+ return response.projectNumber;
216
+ }
217
+
218
+ async getIamPolicy(projectId) {
219
+ return this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}:getIamPolicy`, {
220
+ method: 'POST',
221
+ body: JSON.stringify({ options: { requestedPolicyVersion: 3 } }),
222
+ });
223
+ }
224
+
225
+ async setIamPolicy(projectId, policy) {
226
+ return this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}:setIamPolicy`, {
227
+ method: 'POST',
228
+ body: JSON.stringify({ policy }),
229
+ });
230
+ }
231
+
232
+ // ===========================================================================
233
+ // SERVICE USAGE — Google Cloud API enablement
234
+ // ===========================================================================
235
+
236
+ /** Names (e.g. 'firebase.googleapis.com') of all ENABLED services */
237
+ async listEnabledServices(projectId) {
238
+ const projectNumber = await this.getProjectNumber(projectId);
239
+ const enabled = [];
240
+ let pageToken = '';
241
+
242
+ do {
243
+ const url = `${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services?filter=state:ENABLED&pageSize=200${pageToken ? `&pageToken=${pageToken}` : ''}`;
244
+ const response = await this.request(url);
245
+ for (const service of response.services || []) {
246
+ // name is 'projects/{n}/services/{serviceName}'
247
+ enabled.push(service.name.split('/').pop());
248
+ }
249
+ pageToken = response.nextPageToken || '';
250
+ } while (pageToken);
251
+
252
+ return enabled;
253
+ }
254
+
255
+ async isServiceEnabled(projectId, serviceName) {
256
+ const projectNumber = await this.getProjectNumber(projectId);
257
+ try {
258
+ const response = await this.request(`${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services/${serviceName}`);
259
+ return response.state === 'ENABLED';
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+
265
+ async enableService(projectId, serviceName) {
266
+ const projectNumber = await this.getProjectNumber(projectId);
267
+ const response = await this.request(`${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services/${serviceName}:enable`, {
268
+ method: 'POST',
269
+ body: JSON.stringify({}),
270
+ });
271
+
272
+ if (response.name) {
273
+ await this.waitForOperation(response.name, SERVICE_USAGE_API_BASE);
274
+ }
275
+
276
+ return { enabled: true, service: serviceName };
277
+ }
278
+
279
+ async enableServices(projectId, serviceNames) {
280
+ const projectNumber = await this.getProjectNumber(projectId);
281
+ const response = await this.request(`${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services:batchEnable`, {
282
+ method: 'POST',
283
+ body: JSON.stringify({ serviceIds: serviceNames }),
284
+ });
285
+
286
+ if (response.name) {
287
+ await this.waitForOperation(response.name, SERVICE_USAGE_API_BASE);
288
+ }
289
+
290
+ return { enabled: true, services: serviceNames };
291
+ }
292
+
293
+ // ===========================================================================
294
+ // IAM — service accounts
295
+ // ===========================================================================
296
+
297
+ async listServiceAccounts(projectId) {
298
+ try {
299
+ const response = await this.request(`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts`);
300
+ return response.accounts || [];
301
+ } catch {
302
+ return [];
303
+ }
304
+ }
305
+
306
+ async createServiceAccount(projectId, accountId, displayName) {
307
+ return this.request(`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts`, {
308
+ method: 'POST',
309
+ body: JSON.stringify({
310
+ accountId,
311
+ serviceAccount: { displayName },
312
+ }),
313
+ });
314
+ }
315
+
316
+ /**
317
+ * Create and download a service account key — returned decoded (Google
318
+ * only shows keys ONCE at creation; they cannot be re-downloaded)
319
+ */
320
+ async createServiceAccountKey(projectId, serviceAccountEmail) {
321
+ const response = await this.request(`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${serviceAccountEmail}/keys`, {
322
+ method: 'POST',
323
+ body: JSON.stringify({ privateKeyType: 'TYPE_GOOGLE_CREDENTIALS_FILE' }),
324
+ });
325
+
326
+ if (response.privateKeyData) {
327
+ return JSON.parse(Buffer.from(response.privateKeyData, 'base64').toString('utf8'));
328
+ }
329
+
330
+ return response;
331
+ }
332
+
333
+ // ===========================================================================
334
+ // BILLING — Blaze plan
335
+ // ===========================================================================
336
+
337
+ async getProjectBillingInfo(projectId) {
338
+ try {
339
+ return await this.request(`${CLOUD_BILLING_API_BASE}/projects/${projectId}/billingInfo`);
340
+ } catch {
341
+ return null;
342
+ }
343
+ }
344
+
345
+ /** Billing accounts visible to the authed user (open + closed; callers mark closed) */
346
+ async listBillingAccounts() {
347
+ const response = await this.request(`${CLOUD_BILLING_API_BASE}/billingAccounts`);
348
+ return response.billingAccounts || [];
349
+ }
350
+
351
+ async linkBillingAccount(projectId, billingAccountName) {
352
+ return this.request(`${CLOUD_BILLING_API_BASE}/projects/${projectId}/billingInfo`, {
353
+ method: 'PUT',
354
+ body: JSON.stringify({ billingAccountName }),
355
+ });
356
+ }
357
+
358
+ // ===========================================================================
359
+ // IAP — OAuth consent screen (brand)
360
+ // ===========================================================================
361
+
362
+ async listBrands(projectId) {
363
+ try {
364
+ const response = await this.request(`https://iap.googleapis.com/v1/projects/${projectId}/brands`);
365
+ return response.brands || [];
366
+ } catch {
367
+ return [];
368
+ }
369
+ }
370
+
371
+ /**
372
+ * supportEmail must be the caller's email or a Google Group they own.
373
+ *
374
+ * The AUDIENCE cannot be asked for (#667, proven live): `orgInternalOnly`
375
+ * is output-only, an API-made brand is born Internal whatever the body
376
+ * says, and the create itself is org-only (an org-less project answers
377
+ * 400 "Project must belong to an organization"). The caller takes the
378
+ * returned brand through the audience stopper.
379
+ */
380
+ async createBrand(projectId, applicationTitle, supportEmail) {
381
+ return this.request(`https://iap.googleapis.com/v1/projects/${projectId}/brands`, {
382
+ method: 'POST',
383
+ body: JSON.stringify({ applicationTitle, supportEmail }),
384
+ });
385
+ }
386
+
387
+ /**
388
+ * Email of the AUTHORIZING user — the only always-valid consent-screen
389
+ * supportEmail. Null when the cached token predates the userinfo.email
390
+ * scope (re-consent adds it).
391
+ */
392
+ async getAuthenticatedEmail() {
393
+ try {
394
+ const info = await this.request('https://www.googleapis.com/oauth2/v2/userinfo');
395
+ return info?.email || null;
396
+ } catch {
397
+ return null;
398
+ }
399
+ }
400
+
401
+ // ===========================================================================
402
+ // IDENTITY TOOLKIT — authentication configuration
403
+ // ===========================================================================
404
+
405
+ /** Upgrade Firebase Auth to Identity Platform (requires Blaze) */
406
+ async initializeIdentityPlatform(projectId) {
407
+ return this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/identityPlatform:initializeAuth`, {
408
+ method: 'POST',
409
+ body: JSON.stringify({}),
410
+ });
411
+ }
412
+
413
+ async getIdentityConfig(projectId) {
414
+ try {
415
+ return await this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/config`);
416
+ } catch {
417
+ return null;
418
+ }
419
+ }
420
+
421
+ async updateIdentityConfig(projectId, config) {
422
+ const updateMask = Object.keys(config).join(',');
423
+ return this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/config?updateMask=${updateMask}`, {
424
+ method: 'PATCH',
425
+ body: JSON.stringify(config),
426
+ });
427
+ }
428
+
429
+ async getIdpConfig(projectId, idpId) {
430
+ try {
431
+ return await this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/defaultSupportedIdpConfigs/${idpId}`);
432
+ } catch {
433
+ return null;
434
+ }
435
+ }
436
+
437
+ // ===========================================================================
438
+ // FIRESTORE
439
+ // ===========================================================================
440
+
441
+ async getFirestoreDatabase(projectId, databaseId = '(default)') {
442
+ try {
443
+ return await this.request(`${FIRESTORE_API_BASE}/projects/${projectId}/databases/${databaseId}`);
444
+ } catch {
445
+ return null;
446
+ }
447
+ }
448
+
449
+ async createFirestoreDatabase(projectId, locationId = 'nam5') {
450
+ const response = await this.request(`${FIRESTORE_API_BASE}/projects/${projectId}/databases?databaseId=(default)`, {
451
+ method: 'POST',
452
+ body: JSON.stringify({
453
+ type: 'FIRESTORE_NATIVE',
454
+ locationId,
455
+ }),
456
+ });
457
+
458
+ if (response.name) {
459
+ await this.waitForOperation(response.name, FIRESTORE_API_BASE);
460
+ }
461
+
462
+ return response;
463
+ }
464
+
465
+ /** Enable Point-in-Time Recovery (7-day disaster recovery) */
466
+ async enableFirestorePITR(projectId, databaseId = '(default)') {
467
+ const response = await this.request(`${FIRESTORE_API_BASE}/projects/${projectId}/databases/${databaseId}?updateMask=pointInTimeRecoveryEnablement`, {
468
+ method: 'PATCH',
469
+ body: JSON.stringify({
470
+ pointInTimeRecoveryEnablement: 'POINT_IN_TIME_RECOVERY_ENABLED',
471
+ }),
472
+ });
473
+
474
+ // Firestore answers this PATCH with an already-finished operation
475
+ // (done: true, the updated Database embedded) and purges the operation
476
+ // record immediately — polling its name 404s "Operation does not exist"
477
+ // from the first attempt (#164, recorded live 2026-08-03). Poll only a
478
+ // genuinely unfinished operation, and surface a finished one's error the
479
+ // way the poller would have.
480
+ if (response.done && response.error) {
481
+ throw new Error(`Operation failed: ${response.error.message}`);
482
+ }
483
+ if (!response.done && response.name?.includes('operations')) {
484
+ await this.waitForOperation(response.name, FIRESTORE_API_BASE);
485
+ }
486
+
487
+ return response;
488
+ }
489
+
490
+ // ===========================================================================
491
+ // REALTIME DATABASE
492
+ // ===========================================================================
493
+
494
+ async listRealtimeDatabases(projectId) {
495
+ try {
496
+ const response = await this.request(`https://firebasedatabase.googleapis.com/v1beta/projects/${projectId}/locations/-/instances`);
497
+ return response.instances || [];
498
+ } catch {
499
+ return [];
500
+ }
501
+ }
502
+
503
+ /** The default instance must be named {projectId}-default-rtdb */
504
+ async createRealtimeDatabase(projectId, locationId = 'us-central1') {
505
+ const databaseId = `${projectId}-default-rtdb`;
506
+ return this.request(`https://firebasedatabase.googleapis.com/v1beta/projects/${projectId}/locations/${locationId}/instances?databaseId=${databaseId}`, {
507
+ method: 'POST',
508
+ body: JSON.stringify({ type: 'DEFAULT_DATABASE' }),
509
+ });
510
+ }
511
+
512
+ // ===========================================================================
513
+ // STORAGE
514
+ // ===========================================================================
515
+
516
+ async getStorageBucket(projectId) {
517
+ try {
518
+ const response = await this.request(`https://firebasestorage.googleapis.com/v1beta/projects/${projectId}/defaultBucket`);
519
+ return response?.bucket || null;
520
+ } catch {
521
+ return null;
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Create the default bucket via the Firebase Storage API (new buckets are
527
+ * {projectId}.firebasestorage.app; pre-Oct-2024 ones {projectId}.appspot.com)
528
+ */
529
+ async createDefaultStorageBucket(projectId, location = 'us-central1') {
530
+ const existing = await this.getStorageBucket(projectId);
531
+ if (existing?.name) {
532
+ return { alreadyLinked: true, bucket: existing.name };
533
+ }
534
+
535
+ try {
536
+ const response = await this.request(`https://firebasestorage.googleapis.com/v1beta/projects/${projectId}/defaultBucket`, {
537
+ method: 'POST',
538
+ body: JSON.stringify({
539
+ location,
540
+ storageClass: 'STANDARD',
541
+ }),
542
+ });
543
+
544
+ const bucketName = response?.bucket?.name || response?.name;
545
+ return { created: true, bucket: bucketName };
546
+ } catch (error) {
547
+ if (error.message?.includes('already') || error.message?.includes('409')) {
548
+ const bucket = await this.getStorageBucket(projectId);
549
+ return { alreadyLinked: true, bucket: bucket?.name };
550
+ }
551
+
552
+ throw error;
553
+ }
554
+ }
555
+
556
+ // ===========================================================================
557
+ // HOSTING — sites + custom domains
558
+ // ===========================================================================
559
+
560
+ async listHostingSites(projectId) {
561
+ const response = await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites`);
562
+ return response.sites || [];
563
+ }
564
+
565
+ async createHostingSite(projectId, siteId) {
566
+ return this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites?siteId=${siteId}`, {
567
+ method: 'POST',
568
+ body: JSON.stringify({}),
569
+ });
570
+ }
571
+
572
+ async getCustomDomain(projectId, siteId, domainName) {
573
+ const encodedDomain = encodeURIComponent(domainName);
574
+ try {
575
+ return await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites/${siteId}/customDomains/${encodedDomain}`);
576
+ } catch {
577
+ return null;
578
+ }
579
+ }
580
+
581
+ async createCustomDomain(projectId, siteId, domainName) {
582
+ const encodedDomain = encodeURIComponent(domainName);
583
+ const response = await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites/${siteId}/customDomains?customDomainId=${encodedDomain}`, {
584
+ method: 'POST',
585
+ body: JSON.stringify({}),
586
+ });
587
+
588
+ return this.settleDomainClaim(response);
589
+ }
590
+
591
+ async undeleteCustomDomain(projectId, siteId, domainName) {
592
+ const encodedDomain = encodeURIComponent(domainName);
593
+ const response = await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites/${siteId}/customDomains/${encodedDomain}:undelete`, {
594
+ method: 'POST',
595
+ body: JSON.stringify({}),
596
+ });
597
+
598
+ return this.settleDomainClaim(response);
599
+ }
600
+
601
+ /**
602
+ * Wait on a domain claim's operation, briefly. The claim only completes once
603
+ * DNS verification lands (hours away, normally), so the budget is short: long
604
+ * enough to surface an operation that fails outright, then the claim is
605
+ * reported PENDING (#56) and hosting's own verification poller takes over.
606
+ */
607
+ async settleDomainClaim(response) {
608
+ if (!response.name?.includes('operations')) {
609
+ return response;
610
+ }
611
+
612
+ const outcome = await this.waitForOperation(response.name, FIREBASE_HOSTING_API_BASE, DOMAIN_CLAIM_POLL);
613
+
614
+ return outcome?.pending ? { ...response, pending: true } : response;
615
+ }
616
+
617
+ /**
618
+ * Domain verification status:
619
+ * { exists, verified, deleted?, ownershipState, hostState, certState, requiredDnsUpdates }
620
+ */
621
+ async checkDomainStatus(projectId, siteId, domainName) {
622
+ const domain = await this.getCustomDomain(projectId, siteId, domainName);
623
+
624
+ if (!domain) {
625
+ return { verified: false, exists: false };
626
+ }
627
+
628
+ if (domain.deleteTime) {
629
+ return { verified: false, exists: false, deleted: true };
630
+ }
631
+
632
+ const ownershipActive = domain.ownershipState === 'OWNERSHIP_ACTIVE';
633
+ const hostActive = domain.hostState === 'HOST_ACTIVE';
634
+ const verified = ownershipActive && hostActive;
635
+
636
+ // requiredDnsUpdates: { discovered: [...], desired: [...] } — we want desired
637
+ let dnsUpdates = [];
638
+ if (domain.requiredDnsUpdates) {
639
+ if (Array.isArray(domain.requiredDnsUpdates.desired)) {
640
+ dnsUpdates = domain.requiredDnsUpdates.desired;
641
+ } else if (Array.isArray(domain.requiredDnsUpdates)) {
642
+ dnsUpdates = domain.requiredDnsUpdates;
643
+ }
644
+ }
645
+
646
+ // ACME challenge for SSL rides in cert.verification.dns.desired
647
+ if (Array.isArray(domain.cert?.verification?.dns?.desired)) {
648
+ dnsUpdates = [...dnsUpdates, ...domain.cert.verification.dns.desired];
649
+ }
650
+
651
+ return {
652
+ exists: true,
653
+ verified,
654
+ ownershipState: domain.ownershipState,
655
+ certState: domain.certState,
656
+ hostState: domain.hostState,
657
+ requiredDnsUpdates: dnsUpdates,
658
+ };
659
+ }
660
+ }
661
+
662
+ module.exports = { FirebaseAPI };