@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,102 @@
1
+ /**
2
+ * Interactive Firebase project selection/creation (config-landing flow):
3
+ * when cloud.config.projectId is missing in an interactive run, pick from the
4
+ * projects the authed user can see, or create one — project id + display
5
+ * name prompted (brand id/name as defaults), with the 30-project quota
6
+ * shown. The chosen id lands in omega.json5 (comment-preserving writeback).
7
+ *
8
+ * The organization is tri-state (#33): cloud.organizationId set → the
9
+ * project is created inside it (that's what gives the compute service
10
+ * account its default roles); `false` → standalone, no questions; missing →
11
+ * pick from the orgs the authed user can see or opt out — either answer
12
+ * lands in omega.json5, so it's a one-time question. Downloaders without an
13
+ * org onboard cleanly.
14
+ */
15
+ const chalk = require('chalk').default;
16
+ const { input } = require('../../../vendor/devkit/prompt.js');
17
+ const { resolveConfigValue } = require('../../../lib/config-flow.js');
18
+
19
+ const PROJECT_QUOTA = 30;
20
+ const PROJECT_ID_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
21
+
22
+ /**
23
+ * Resolve (or interactively land) the organization for a NEW project.
24
+ * Chained inside the create flow the user already said Yes to, so there is
25
+ * no Yes/Skip gate — just the selection (orgs + a standalone opt-out).
26
+ *
27
+ * @param {Object} context - Service context
28
+ * @param {Object} api - FirebaseAPI client
29
+ * @returns {Promise<string|null>} - organization id, or null for standalone
30
+ */
31
+ async function resolveOrganization(context, api) {
32
+ return resolveConfigValue(context, {
33
+ path: 'cloud.organizationId',
34
+ label: 'Google Cloud organization',
35
+ gate: false,
36
+ message: 'Create the project inside a Google Cloud organization?',
37
+ choices: () => api.listOrganizations(),
38
+ getName: (org) => `${org.displayName} (${org.name.replace('organizations/', '')})`,
39
+ getValue: (org) => org.name.replace('organizations/', ''),
40
+ optOut: { label: 'No organization — create it standalone' },
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Resolve (or interactively land) the Firebase project id.
46
+ *
47
+ * @param {Object} context - Service context
48
+ * @param {Object} api - FirebaseAPI client
49
+ * @returns {Promise<string|null>} - projectId, or null when skipped
50
+ */
51
+ async function resolveFirebaseProject(context, api) {
52
+ return resolveConfigValue(context, {
53
+ path: 'cloud.config.projectId',
54
+ label: 'Firebase project',
55
+ choices: () => api.listProjects(),
56
+ getName: (project) => `${project.displayName} (${project.projectId})`,
57
+ getValue: (project) => project.projectId,
58
+ createNew: {
59
+ label: 'Firebase project',
60
+ handler: async () => {
61
+ const { brand } = context.brandConfig;
62
+
63
+ const projects = await api.listProjects();
64
+ const remaining = PROJECT_QUOTA - projects.length;
65
+ const color = remaining <= 5 ? chalk.yellow : chalk.dim;
66
+ const icon = remaining <= 5 ? '⚠' : '→';
67
+ console.log(` ${color(`${icon} Firebase project quota: ${projects.length}/${PROJECT_QUOTA} (${remaining} remaining)`)}`);
68
+
69
+ const projectId = await input({
70
+ message: 'Enter project ID (lowercase, numbers, hyphens only):',
71
+ default: brand.id,
72
+ validate: (val) => {
73
+ if (!val?.trim()) {
74
+ return 'Project ID is required';
75
+ }
76
+ if (!PROJECT_ID_PATTERN.test(val)) {
77
+ return 'Project ID must be 6-30 chars, start with a letter, only lowercase letters, numbers, hyphens';
78
+ }
79
+ return true;
80
+ },
81
+ });
82
+
83
+ const displayName = await input({
84
+ message: 'Enter display name:',
85
+ default: brand.name,
86
+ });
87
+
88
+ const organizationId = await resolveOrganization(context, api);
89
+ console.log(` Creating Firebase project ${chalk.cyan(projectId)}...`);
90
+ if (organizationId) {
91
+ console.log(` ${chalk.dim('→')} Creating inside organization ${chalk.cyan(organizationId)}`);
92
+ }
93
+ await api.createProject(projectId, displayName, organizationId);
94
+ console.log(` ${chalk.green('✓')} Created`);
95
+
96
+ return projectId;
97
+ },
98
+ },
99
+ });
100
+ }
101
+
102
+ module.exports = { resolveFirebaseProject };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Push the brand's directory entry to the parent project's Firestore at
3
+ * brands/{brand.id}: identity plus the blocks the brand declares.
4
+ *
5
+ * Diff FIRST — an unchanged config performs zero writes, which is what makes
6
+ * this safe to run on every manage walk.
7
+ *
8
+ * MERGE-write, not replace (the server service's registry is a replace, and
9
+ * the difference is deliberate): the framework owns only its own sections of
10
+ * the entry, so the write masks exactly those. Whatever the hub keeps on the
11
+ * same document — order counts, moderation state, anything ITW's marketplace
12
+ * adds — survives every push, and a section the brand drops from config is
13
+ * still removed, because the mask names every owned section whether or not it
14
+ * has a value this run.
15
+ */
16
+ const { isDeepStrictEqual } = require('node:util');
17
+ const chalk = require('chalk').default;
18
+
19
+ const { OWNED_SECTIONS, buildEntry, ownedSlice } = require('../lib/blocks.js');
20
+
21
+ module.exports = async function ensureEntry(context) {
22
+ const { brandConfig, db, options } = context;
23
+ const dryRun = options?.dryRun || false;
24
+
25
+ const docPath = `brands/${brandConfig.brand.id}`;
26
+ const desired = buildEntry(brandConfig);
27
+ const blocks = Object.keys(desired).filter((key) => key !== 'brand' && key !== 'github');
28
+
29
+ const current = await db.getDoc(docPath);
30
+
31
+ if (isDeepStrictEqual(ownedSlice(current), desired)) {
32
+ console.log(` ${chalk.green('✓')} Directory entry ${chalk.dim(docPath)} in sync${blocks.length > 0 ? chalk.dim(` (${blocks.join(', ')})`) : ''}`);
33
+ return { output: { entry: { synced: true, blocks } } };
34
+ }
35
+
36
+ const action = current ? 'update' : 'create';
37
+
38
+ if (dryRun) {
39
+ console.log(` ${chalk.yellow('[DRY RUN]')} Would ${action} directory entry ${chalk.cyan(docPath)}`);
40
+ return { output: { entry: { planned: action, blocks } } };
41
+ }
42
+
43
+ await db.patchDoc(docPath, desired, OWNED_SECTIONS);
44
+
45
+ console.log(` ${chalk.green('✓')} Directory entry ${chalk.dim(docPath)} ${action}d${blocks.length > 0 ? chalk.dim(` (${blocks.join(', ')})`) : ''}`);
46
+
47
+ return { output: { entry: { [`${action}d`]: true, blocks } } };
48
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Directory service — the brand PUSHES its own entry into the PARENT
3
+ * project's Firestore `brands` collection (`brands/{brand.id}`) during its
4
+ * manage walk, so whatever the parent runs on top of that collection reads a
5
+ * current directory instead of one that goes stale the day a brand migrates.
6
+ *
7
+ * GENERIC and opt-in (#246). The framework owes the fresh ENTRY and nothing
8
+ * else: the marketplace/hub that consumes it — ITW's guest-post sponsorship
9
+ * marketplace is the first — is brand code and never enters the framework.
10
+ * The payload is brand identity plus the opt-in BLOCKS the brand declares
11
+ * (lib/blocks.js); `sponsorships` is the first block. Field mapping from
12
+ * legacy omega-manager: docs/manager/directory.md.
13
+ *
14
+ * Gates, in order — every one of them is a clean skip:
15
+ * 1. `directory.enabled: true`. Absent config never pushes: the parent
16
+ * declares the collection world-readable in its own rules (the operator
17
+ * step in docs/manager/directory.md), so participation is explicit or
18
+ * nothing.
19
+ * 2. `parent` names the relationship. No parent (or `parent: false`) means
20
+ * there is no directory to push INTO.
21
+ * 3. Not a demo-* (emulator-only) brand — the cloud service's gate, for the
22
+ * same reason: an offline fixture must never reach a live project.
23
+ *
24
+ * Auth: DIRECTORY_SERVICE_ACCOUNT in the brand .env — a path to the PARENT
25
+ * project's Firebase service-account JSON (absolute, or relative to the brand
26
+ * root), the server service's mechanism exactly. The service account names the
27
+ * target project; no config key ever holds a credential.
28
+ */
29
+ const { isDemoProject } = require('../../vendor/config/index.js');
30
+
31
+ const { createServiceRunner } = require('../../lib/service-runner.js');
32
+ const { FirestoreREST, loadServiceAccount } = require('../../lib/firestore-rest.js');
33
+
34
+ module.exports.run = createServiceRunner({
35
+ serviceDir: __dirname,
36
+ setup: async (context) => {
37
+ const { brandConfig } = context;
38
+
39
+ if (brandConfig.directory?.enabled !== true) {
40
+ return { skip: true, reason: 'directory.enabled is not true (the brand does not participate in a directory)' };
41
+ }
42
+
43
+ if (!brandConfig.parent) {
44
+ return { skip: true, reason: 'no parent configured — there is no parent directory to push into' };
45
+ }
46
+
47
+ const projectId = brandConfig.cloud?.config?.projectId;
48
+ if (isDemoProject(projectId)) {
49
+ return { skip: true, reason: `${projectId} is a demo-* (emulator-only) project — no live directory to push into` };
50
+ }
51
+
52
+ // Tests inject a fake client via context.directoryDb
53
+ let db = context.directoryDb;
54
+ if (!db) {
55
+ const envPath = process.env.DIRECTORY_SERVICE_ACCOUNT;
56
+ if (!envPath) {
57
+ return { skip: true, reason: 'no DIRECTORY_SERVICE_ACCOUNT configured (path to the parent project\'s service-account JSON, set it in the brand .env)' };
58
+ }
59
+ db = new FirestoreREST(loadServiceAccount(envPath, context.brandRoot));
60
+ }
61
+
62
+ return { db };
63
+ },
64
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The directory payload — brand IDENTITY plus the opt-in BLOCKS the brand
3
+ * declares — and the list of sections the framework OWNS in the entry.
4
+ *
5
+ * Identity is not optional: a directory entry with no name addresses nothing.
6
+ * The repo slugs come from @omega.js/config's one derivation (#290) — the same
7
+ * `{ owner, name, repo }` the backend hands consumers as
8
+ * `config.resolved.github` — never re-derived here.
9
+ *
10
+ * BLOCKS is the extension point (#246). One entry per publishable block,
11
+ * naming the brand-config section it reads; a block crosses only when the
12
+ * brand DECLARES that section, which is what "opt-in blocks" means. Adding the
13
+ * next block is one entry here plus its schema line in @omega.js/config.
14
+ *
15
+ * Sections are pushed VERBATIM. Nothing is redacted on the way out, because
16
+ * nothing secret can be in them: these are schema-known public config keys and
17
+ * @omega.js/config hard-fails secret-shaped keys before any merge happens.
18
+ */
19
+ const { brandRepo } = require('../../../vendor/config/index.js');
20
+
21
+ // Block name → the brand-config section it publishes.
22
+ const BLOCKS = {
23
+ // Guest-post / link-insertion terms, priced per placement. Legacy
24
+ // omega-manager carried the same shape at `.brands/<id>/config.json`
25
+ // `sponsorships` — the field mapping is in docs/manager/directory.md.
26
+ sponsorships: (config) => config.sponsorships,
27
+ };
28
+
29
+ // Every top-level field of the entry the framework owns, block or not. The
30
+ // write masks ALL of them, so a section the brand drops from config is
31
+ // removed from the entry too — and every OTHER field of the document (whatever
32
+ // the hub itself owns: order counts, moderation state) survives untouched.
33
+ const OWNED_SECTIONS = ['brand', 'github', ...Object.keys(BLOCKS)];
34
+
35
+ /**
36
+ * Whether a config section is declared richly enough to publish. An absent
37
+ * section and an empty one say the same thing: the brand is not offering it.
38
+ *
39
+ * @param {*} section - The raw config value.
40
+ * @returns {boolean}
41
+ */
42
+ function isDeclared(section) {
43
+ return Boolean(section)
44
+ && typeof section === 'object'
45
+ && !Array.isArray(section)
46
+ && Object.keys(section).length > 0;
47
+ }
48
+
49
+ /**
50
+ * Build the directory entry for a brand.
51
+ *
52
+ * @param {object} config - The resolved brand config.
53
+ * @returns {object} The entry document — only the sections that resolve.
54
+ */
55
+ function buildEntry(config) {
56
+ const entry = {
57
+ brand: {
58
+ id: config.brand.id,
59
+ name: config.brand.name,
60
+ },
61
+ };
62
+
63
+ if (config.brand.url) {
64
+ entry.brand.url = config.brand.url;
65
+ }
66
+
67
+ // '' unless BOTH halves resolve (#290) — half an address addresses nothing,
68
+ // and an entry is better without the key than with a broken one.
69
+ const github = brandRepo(config);
70
+ if (github.repo) {
71
+ entry.github = github;
72
+ }
73
+
74
+ for (const [name, read] of Object.entries(BLOCKS)) {
75
+ const section = read(config);
76
+ if (isDeclared(section)) {
77
+ entry[name] = section;
78
+ }
79
+ }
80
+
81
+ return entry;
82
+ }
83
+
84
+ /**
85
+ * The framework-owned slice of the entry as it stands in the directory today —
86
+ * what `buildEntry` output is diffed against. Everything else in the document
87
+ * belongs to the hub and is not this service's business.
88
+ *
89
+ * @param {object|null} current - The document read from the directory.
90
+ * @returns {object} Only the owned sections that are present.
91
+ */
92
+ function ownedSlice(current) {
93
+ const slice = {};
94
+
95
+ for (const key of OWNED_SECTIONS) {
96
+ if (current && current[key] !== undefined) {
97
+ slice[key] = current[key];
98
+ }
99
+ }
100
+
101
+ return slice;
102
+ }
103
+
104
+ module.exports = { BLOCKS, OWNED_SECTIONS, buildEntry, ownedSlice };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Disperse service — the remnant of omega-manager's disperse after the
3
+ * config hierarchy dissolved file dispersal: targets read config/omega.json5
4
+ * directly, so there is no .brands/ mirror and no per-repo config writes
5
+ * (the old repos/products/pricing-md/cdn operations). What can't ride the
6
+ * hierarchy is exactly what this service still moves:
7
+ *
8
+ * certs — signing artifacts (binary, never mergeable) copied from the
9
+ * certificates service's .omega/certificates/apple/ tree into
10
+ * each desktop/mobile target's certs dir. The copy itself is
11
+ * devkit's (`@omega.js/devkit/certs`), so a desktop build reaches
12
+ * the same artifacts through the same rules (#678).
13
+ *
14
+ * The .env composition is GONE (#678): the brand-root .env is the one file
15
+ * humans and the manager edit, and every verb composes its target's runtime
16
+ * env from the cascade by schema — no machine writes a target .env. A CUSTOM
17
+ * target has no @omega.js/config to walk the cascade for it, so it inherits
18
+ * instead: manage.js loads the env chain into process.env before it spawns
19
+ * anything, and a custom target started by `omega dev`/`omega deploy` gets the
20
+ * brand keys that way. A standalone run inside the target dir does not.
21
+ *
22
+ * De-ITW'd from omega-manager: the company-wide .output/_shared/ cert tree
23
+ * and .brands/{id}/.env override layer are gone — artifacts are brand-local
24
+ * (.omega/certificates/apple/) and manage.js already layers shell env >
25
+ * brand .env > company .env before any service runs.
26
+ *
27
+ * Runs on the DELIVERY lane (config.js BOOT_SERVICES): every `omega dev`
28
+ * boot, every brand-root `omega deploy`, and the full manage walk — after
29
+ * certificates (the artifacts must exist) and before update (builds read the
30
+ * cert files).
31
+ */
32
+ const { createServiceRunner } = require('../../lib/service-runner.js');
33
+
34
+ module.exports.run = createServiceRunner({
35
+ serviceDir: __dirname,
36
+ setup: (context) => {
37
+ const mappedTargets = (context.targets || []).filter((entry) => entry.target);
38
+
39
+ if (mappedTargets.length === 0) {
40
+ return { skip: true, reason: 'no target-mapped dirs' };
41
+ }
42
+
43
+ return { mappedTargets };
44
+ },
45
+ });
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Copy signing artifacts into desktop/mobile targets.
3
+ *
4
+ * The copy itself is devkit's (`@omega.js/devkit/certs`, #678) — the ONE
5
+ * delivery step every verb rides, so a desktop build reaches the same
6
+ * artifacts through the same rules. What lives here is the manage-lane
7
+ * FRAMING: which targets get a pass, the certificates config gate, and the
8
+ * per-rule reporting.
9
+ *
10
+ * Sources are the certificates service's outputs under the signing tree —
11
+ * {companyRoot||brandRoot}/.omega/certificates/apple/ (company-managed
12
+ * brands share the company workspace's material); destinations are each
13
+ * target's certs dir. Optional rules skip silently when the source is
14
+ * missing; required rules warn (the desktop/mobile build would be unsigned).
15
+ * A brand with no Apple artifacts at all (certificates disabled or never run)
16
+ * is a quiet note, not a warning.
17
+ *
18
+ * A miss is warn-only: the dests are documented human drop points, so a dest
19
+ * with no source in the tree is left exactly where the operator put it.
20
+ */
21
+ const { join } = require('node:path');
22
+ const jetpack = require('fs-jetpack');
23
+ const chalk = require('chalk').default;
24
+
25
+ const { deliverCerts, resolveCertRules, certsSourceDir, CERT_FILE_MAP } = require('../../../vendor/devkit/certs.js');
26
+
27
+ module.exports = async (context) => {
28
+ const { brandRoot, companyRoot, brandConfig, mappedTargets, options = {} } = context;
29
+
30
+ const certConfig = brandConfig.certificates;
31
+ if (certConfig === false || certConfig?.enabled === false) {
32
+ console.log(` ${chalk.dim('⊘ certificates.enabled = false — no signing artifacts to disperse')}`);
33
+ return { output: { certs: { reason: 'certificates.enabled = false' } } };
34
+ }
35
+
36
+ const certTargets = mappedTargets.filter((entry) => CERT_FILE_MAP[entry.target]);
37
+ if (certTargets.length === 0) {
38
+ console.log(` ${chalk.dim('⊘ no desktop or mobile targets')}`);
39
+ return { output: { certs: { reason: 'no desktop or mobile targets' } } };
40
+ }
41
+
42
+ // The certificates service's signing tree — company-shared when the brand
43
+ // is company-managed, brand-local otherwise (the same resolution rule)
44
+ const sourceRoot = companyRoot || brandRoot;
45
+ const appleDir = certsSourceDir(sourceRoot);
46
+ if (!jetpack.exists(appleDir)) {
47
+ console.log(` ${chalk.dim('⊘ no signing artifacts yet (.omega/certificates/apple is empty — the certificates service produces them)')}`);
48
+ return { output: { certs: { reason: 'no signing artifacts yet' } } };
49
+ }
50
+
51
+ const brand = brandConfig.brand;
52
+ let copied = 0;
53
+ let current = 0;
54
+ let skipped = 0;
55
+ let warned = 0;
56
+ let planned = 0;
57
+
58
+ for (const entry of certTargets) {
59
+ console.log(` ${chalk.cyan(entry.dir)}:`);
60
+
61
+ const result = deliverCerts({
62
+ sourceRoot,
63
+ targetDir: entry.path,
64
+ target: entry.target,
65
+ brand,
66
+ dryRun: options.dryRun,
67
+ });
68
+ const delivered = new Set(result.copied);
69
+ const unchanged = new Set(result.current);
70
+
71
+ // The rules in their declared order — the delivery's own arrays carry the
72
+ // outcome, the rules carry what a miss MEANS (optional vs required)
73
+ for (const rule of resolveCertRules({ target: entry.target, brand })) {
74
+ const destRel = rule.destRel || rule.dest;
75
+
76
+ if (unchanged.has(destRel)) {
77
+ console.log(` ${chalk.dim(`✓ ${destRel} (current)`)}`);
78
+ current++;
79
+ continue;
80
+ }
81
+
82
+ if (delivered.has(destRel)) {
83
+ if (options.dryRun) {
84
+ console.log(` ${chalk.cyan('[DRY RUN]')} Would copy ${chalk.cyan(destRel)}`);
85
+ planned++;
86
+ } else {
87
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(destRel)}`);
88
+ copied++;
89
+ }
90
+ continue;
91
+ }
92
+
93
+ // A miss: an unset env placeholder (the rule has no real path) or a
94
+ // source that isn't there
95
+ const detail = rule.destRel === null
96
+ ? `env placeholder unset in ${rule.source}`
97
+ : `source missing: ${join(appleDir, rule.sourceRel)}`;
98
+
99
+ if (rule.optional) {
100
+ console.log(` ${chalk.dim(`⊘ ${destRel} (${rule.destRel === null ? 'env placeholder unset' : 'source missing'}, optional)`)}`);
101
+ skipped++;
102
+ } else {
103
+ console.log(` ${chalk.yellow('⚠')} ${chalk.cyan(destRel)} ${chalk.dim(`(${detail})`)}`);
104
+ warned++;
105
+ }
106
+ }
107
+ }
108
+
109
+ const summary = { copied, current, skipped, warned, ...(options.dryRun ? { planned } : {}) };
110
+ console.log(` Summary: ${chalk.bold(copied)} copied, ${current} current, ${chalk.dim(skipped)} skipped, ${warned > 0 ? chalk.yellow(warned) : warned} warnings`);
111
+
112
+ return {
113
+ status: warned > 0 ? 'warned' : 'success',
114
+ ...(warned > 0 ? { reason: `${warned} cert file(s) not dispersed` } : {}),
115
+ output: { certs: summary },
116
+ };
117
+ };
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Ensure the registrar's nameservers point to Cloudflare.
3
+ *
4
+ * Flow:
5
+ * 1. Fetch the Cloudflare zone to get its assigned nameservers
6
+ * 2. namecheap: read current nameservers via API, set them when mismatched
7
+ * 3. manual registrars: an active zone proves they're already set (success);
8
+ * a pending zone prints the values to set and returns warned
9
+ *
10
+ * omega-manager split SLD/TLD by popping the last dot-label, which breaks
11
+ * multi-part TLDs (mybrand.co.uk → SLD "mybrand.co", TLD "uk"); this port
12
+ * uses the public suffix list instead.
13
+ */
14
+ const chalk = require('chalk').default;
15
+ const psl = require('psl');
16
+ const { pollWithSpinner } = require('../../../vendor/devkit/flows.js');
17
+ const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
18
+ const { isWhitelistError } = require('../lib/namecheap-api.js');
19
+ const { walkWhitelist } = require('../lib/whitelist-walkthrough.js');
20
+
21
+ // A freshly created zone gets its nameservers within seconds; a zone still
22
+ // bare after this many reads is not going to fill in while we watch (#662).
23
+ const ZONE_NS_READS = 6;
24
+ const ZONE_NS_INTERVAL_MS = 5000;
25
+
26
+ /** The unreadable-nameservers warn — the registrar side steps aside for a run. */
27
+ const warnedRead = (message) => ({
28
+ status: 'warned',
29
+ reason: 'could not read the nameservers at Namecheap',
30
+ output: { nameservers: { error: message } },
31
+ });
32
+
33
+ /**
34
+ * The read failed for something the whitelist walk cannot fix — name it, point
35
+ * at the likeliest cause, and step aside. The walk's own recheck failure lands
36
+ * here too, so the hint stays reachable on that branch (#698).
37
+ */
38
+ function warnUnreadable(message) {
39
+ console.log(` ${chalk.yellow('⚠')} Could not read nameservers at Namecheap${chalk.dim(`: ${message}`)}`);
40
+ console.log(` ${chalk.dim('→')} Domain not in your Namecheap account yet (not purchased?). Rerun after purchase.`);
41
+ return warnedRead(message);
42
+ }
43
+
44
+ module.exports = async function ensureNameservers(context) {
45
+ const { cloudflareApi, namecheapApi, domain, provider, options = {} } = context;
46
+
47
+ // Nameservers live at the REGISTRABLE domain — for subdomain projects
48
+ // (playground.omegajs.dev) that's the parent (omegajs.dev): the zone the
49
+ // cloudflare service manages, and the domain the registrar actually holds.
50
+ const zoneName = psl.get(domain) || domain;
51
+ if (zoneName !== domain) {
52
+ console.log(` ${chalk.dim(`Subdomain project — managing nameservers for parent ${zoneName}`)}`);
53
+ }
54
+
55
+ // === READ: Cloudflare's assigned nameservers ===
56
+ let zone = await cloudflareApi.getZoneByName(zoneName);
57
+
58
+ if (!zone) {
59
+ console.log(` ${chalk.yellow('⚠')} No Cloudflare zone found for ${chalk.cyan(zoneName)} — rerun once the cloudflare service creates it`);
60
+ return { status: 'warned', reason: 'no Cloudflare zone found for the zone', output: { nameservers: { note: 'no Cloudflare zone found' } } };
61
+ }
62
+
63
+ // A zone created earlier in THIS walk (the edge service) may still be
64
+ // initializing — wait for its nameservers instead of leaving the registrar
65
+ // to a second run (#662). Bounded: the poll gives up on its own.
66
+ if ((zone.name_servers || []).length === 0 && canPrompt(options)) {
67
+ zone = await waitForAssignedNameservers(cloudflareApi, zoneName, zone);
68
+ }
69
+
70
+ const required = [...(zone.name_servers || [])].sort();
71
+
72
+ if (required.length === 0) {
73
+ console.log(` ${chalk.yellow('⚠')} Zone exists but has no assigned nameservers yet (still initializing) — rerun`);
74
+ return { status: 'warned', reason: 'the zone has no assigned nameservers yet', output: { nameservers: { note: 'zone has no assigned nameservers yet' } } };
75
+ }
76
+
77
+ console.log(` ${chalk.dim('→')} Cloudflare nameservers: ${required.map((ns) => chalk.cyan(ns)).join(', ')}`);
78
+
79
+ if (provider === 'namecheap') {
80
+ return await ensureNamecheap(zoneName, required, namecheapApi, options);
81
+ }
82
+
83
+ // === Manual registrars — no read API, but the zone status is proof enough:
84
+ // an active zone means the nameservers already point to Cloudflare
85
+ if (zone.status === 'active') {
86
+ console.log(` ${chalk.green('✓')} Zone is active — nameservers already pointing to Cloudflare`);
87
+ return { output: { nameservers: { alreadySet: true } } };
88
+ }
89
+
90
+ console.log(` ${chalk.yellow('⚠')} ${provider} requires manual nameserver configuration`);
91
+ console.log(` Set these nameservers at your registrar:`);
92
+ for (const ns of required) {
93
+ console.log(` ${chalk.cyan(ns)}`);
94
+ }
95
+
96
+ return { status: 'warned', reason: `${provider} requires manual nameserver configuration`, output: { nameservers: { manual: true, provider, required } } };
97
+ };
98
+
99
+ /**
100
+ * Re-read the zone until Cloudflare has assigned its nameservers, bounded to
101
+ * ZONE_NS_READS attempts (ENTER checks now, `s` skips).
102
+ *
103
+ * @returns {Promise<object>} - The zone: the assigned one, or the bare zone
104
+ * handed in when the wait ran out or was skipped.
105
+ */
106
+ async function waitForAssignedNameservers(api, zoneName, zone) {
107
+ let reads = 0;
108
+ let assigned = null;
109
+
110
+ const result = await pollWithSpinner({
111
+ check: async () => {
112
+ reads += 1;
113
+ const fresh = await api.getZoneByName(zoneName);
114
+ if ((fresh?.name_servers || []).length > 0) {
115
+ assigned = fresh;
116
+ return { done: true };
117
+ }
118
+ return reads >= ZONE_NS_READS ? { done: true, error: 'still initializing' } : { done: false };
119
+ },
120
+ intervalMs: ZONE_NS_INTERVAL_MS,
121
+ message: `Waiting for Cloudflare to assign nameservers for ${zoneName}`,
122
+ indent: ' ',
123
+ });
124
+
125
+ return result.success && assigned ? assigned : zone;
126
+ }
127
+
128
+ async function ensureNamecheap(domain, required, api, options) {
129
+ const { sld, tld } = psl.parse(domain);
130
+
131
+ // Read current nameservers. Fails for domains not in the Namecheap account
132
+ // (e.g. not purchased yet) — warn instead of failing the brand.
133
+ console.log(` ${chalk.dim('→')} Checking current nameservers at Namecheap...`);
134
+ let current;
135
+ try {
136
+ current = await api.getDns(sld, tld);
137
+ } catch (error) {
138
+ // The IP-whitelist rejection is the ONE read failure this walk can fix in
139
+ // place (#698): open the API access page, then recheck the refused read.
140
+ if (!isWhitelistError(error)) {
141
+ return warnUnreadable(error.message);
142
+ }
143
+
144
+ const recovered = await walkWhitelist(error, () => api.getDns(sld, tld), options);
145
+ if (recovered.error) {
146
+ // The recheck cleared the whitelist and failed for its own reason — that
147
+ // error is the one to report; the pre-walk rejection is stale (#698).
148
+ return warnUnreadable(recovered.error);
149
+ }
150
+ if (!recovered.success) {
151
+ return warnedRead(error.message);
152
+ }
153
+ current = recovered.result;
154
+ }
155
+
156
+ const currentNs = [...current.nameservers].sort();
157
+ const alreadySet = required.length === currentNs.length
158
+ && required.every((ns, i) => ns === currentNs[i]);
159
+
160
+ if (alreadySet) {
161
+ console.log(` ${chalk.green('✓')} Nameservers already pointing to Cloudflare`);
162
+ return { output: { nameservers: { alreadySet: true } } };
163
+ }
164
+
165
+ console.log(` ${chalk.dim('→')} Current: ${currentNs.join(', ') || '(default/none)'}`);
166
+
167
+ // === WRITE ===
168
+ if (options.dryRun) {
169
+ return dryRunPlan('set Cloudflare nameservers at Namecheap', { output: { nameservers: { planned: 'update', current: currentNs, required } } });
170
+ }
171
+
172
+ console.log(` ${chalk.dim('→')} Setting Cloudflare nameservers at Namecheap...`);
173
+
174
+ await api.setCustomNameservers(sld, tld, required);
175
+
176
+ console.log(` ${chalk.green('✓')} Nameservers updated at Namecheap → Cloudflare`);
177
+
178
+ return { output: { nameservers: { updated: true, previous: currentNs } } };
179
+ }