@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,154 @@
1
+ /**
2
+ * Schema Validator for Firestore Documents
3
+ *
4
+ * Validates documents against a schema definition.
5
+ * Used by migration handlers to check data integrity.
6
+ *
7
+ * Schema format:
8
+ * {
9
+ * fieldName: {
10
+ * type: 'string' | 'number' | 'boolean' | 'object' | 'array',
11
+ * required: true | false,
12
+ * nullable: true | false, // if true, null is valid even if type doesn't match
13
+ * itemType: 'string', // for arrays, the expected type of items
14
+ * properties: { ... }, // for objects, nested schema
15
+ * }
16
+ * }
17
+ */
18
+
19
+ /**
20
+ * Get the type of a value (more specific than typeof)
21
+ *
22
+ * @param {*} value - Value to check
23
+ * @returns {string} Type string: 'null', 'array', 'object', 'string', 'number', 'boolean', 'undefined'
24
+ */
25
+ function getType(value) {
26
+ if (value === null) {
27
+ return 'null';
28
+ }
29
+ if (Array.isArray(value)) {
30
+ return 'array';
31
+ }
32
+ return typeof value;
33
+ }
34
+
35
+ /**
36
+ * Format expected type string for error messages
37
+ *
38
+ * @param {Object} fieldSchema - Schema for the field
39
+ * @returns {string} Human-readable type string
40
+ */
41
+ function formatExpectedType(fieldSchema) {
42
+ let expected = fieldSchema.type;
43
+
44
+ if (fieldSchema.nullable) {
45
+ expected += '|null';
46
+ }
47
+
48
+ if (fieldSchema.type === 'array' && fieldSchema.itemType) {
49
+ expected = `array<${fieldSchema.itemType}>`;
50
+ if (fieldSchema.nullable) {
51
+ expected += '|null';
52
+ }
53
+ }
54
+
55
+ return expected;
56
+ }
57
+
58
+ /**
59
+ * Validate a single field against its schema
60
+ *
61
+ * @param {*} value - The value to validate
62
+ * @param {Object} fieldSchema - Schema for this field
63
+ * @param {string} fieldPath - Dot-notation path for error messages
64
+ * @returns {Array} Array of error objects
65
+ */
66
+ function validateField(value, fieldSchema, fieldPath) {
67
+ const errors = [];
68
+ const actualType = getType(value);
69
+
70
+ // Check if field is missing
71
+ if (actualType === 'undefined') {
72
+ if (fieldSchema.required) {
73
+ errors.push({
74
+ field: fieldPath,
75
+ expected: formatExpectedType(fieldSchema),
76
+ actual: 'undefined',
77
+ message: 'Missing required field',
78
+ });
79
+ }
80
+ return errors;
81
+ }
82
+
83
+ // Check nullable
84
+ if (actualType === 'null') {
85
+ if (!fieldSchema.nullable) {
86
+ errors.push({
87
+ field: fieldPath,
88
+ expected: formatExpectedType(fieldSchema),
89
+ actual: 'null',
90
+ message: `Expected ${fieldSchema.type}, got null (field is not nullable)`,
91
+ });
92
+ }
93
+ return errors;
94
+ }
95
+
96
+ // Check type match
97
+ if (actualType !== fieldSchema.type) {
98
+ errors.push({
99
+ field: fieldPath,
100
+ expected: formatExpectedType(fieldSchema),
101
+ actual: actualType,
102
+ message: `Expected ${fieldSchema.type}, got ${actualType}`,
103
+ });
104
+ return errors; // Don't check nested if type doesn't match
105
+ }
106
+
107
+ // For arrays, check item types
108
+ if (fieldSchema.type === 'array' && fieldSchema.itemType) {
109
+ for (let i = 0; i < value.length; i++) {
110
+ const itemType = getType(value[i]);
111
+ if (itemType !== fieldSchema.itemType) {
112
+ errors.push({
113
+ field: `${fieldPath}[${i}]`,
114
+ expected: fieldSchema.itemType,
115
+ actual: itemType,
116
+ message: `Array item expected ${fieldSchema.itemType}, got ${itemType}`,
117
+ });
118
+ }
119
+ }
120
+ }
121
+
122
+ // For objects with properties schema, validate nested fields
123
+ if (fieldSchema.type === 'object' && fieldSchema.properties) {
124
+ for (const [propName, propSchema] of Object.entries(fieldSchema.properties)) {
125
+ const propErrors = validateField(value[propName], propSchema, `${fieldPath}.${propName}`);
126
+ errors.push(...propErrors);
127
+ }
128
+ }
129
+
130
+ return errors;
131
+ }
132
+
133
+ /**
134
+ * Validate a document against a schema
135
+ *
136
+ * @param {Object} doc - Document data to validate
137
+ * @param {Object} schema - Schema definition
138
+ * @returns {Object} { valid: boolean, errors: Array<{ field, expected, actual, message }> }
139
+ */
140
+ function validateDocument(doc, schema) {
141
+ const errors = [];
142
+
143
+ for (const [fieldName, fieldSchema] of Object.entries(schema)) {
144
+ const fieldErrors = validateField(doc[fieldName], fieldSchema, fieldName);
145
+ errors.push(...fieldErrors);
146
+ }
147
+
148
+ return {
149
+ valid: errors.length === 0,
150
+ errors,
151
+ };
152
+ }
153
+
154
+ module.exports = { validateDocument };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Land each target project's DSN in
3
+ * targets.<type>.monitoring.providers.sentry.dsn (#425) — the
4
+ * per-surface override every framework reads through the config merge
5
+ * chain. DSNs are public by design (schema-pinned), so config is their
6
+ * home; the writeback is comment-preserving and a converged rerun leaves
7
+ * the file byte-identical. The resolved value always wins: a hand-set
8
+ * stale DSN is drift and gets patched.
9
+ */
10
+ const chalk = require('chalk').default;
11
+ const { writeBrandConfig } = require('../../../lib/config-write.js');
12
+
13
+ module.exports = async function ensureDsn(context) {
14
+ const { sentryApi: api, brandConfig, serviceData } = context;
15
+
16
+ const org = serviceData.org;
17
+ const projects = serviceData.projectMap || {};
18
+ const targets = Object.keys(projects);
19
+
20
+ if (!org || targets.length === 0) {
21
+ console.log(` ${chalk.dim('⊘ No projects resolved — nothing to land')}`);
22
+ return {};
23
+ }
24
+
25
+ const edits = {};
26
+ const dsns = {};
27
+
28
+ for (const target of targets) {
29
+ const { slug } = projects[target];
30
+ const keys = await api.getProjectKeys(org, slug);
31
+ const active = (keys || []).find((key) => key.isActive !== false && key.dsn?.public);
32
+
33
+ if (!active) {
34
+ console.log(` ${chalk.yellow('⚠')} ${chalk.cyan(slug)} has no active client key — create one in Sentry, then rerun`);
35
+ return { status: 'warned', reason: `${slug} has no active client key — create one in Sentry`, output: { dsn: { missingKey: slug } } };
36
+ }
37
+
38
+ const dsn = active.dsn.public;
39
+ dsns[target] = dsn;
40
+
41
+ const current = brandConfig.targets?.[target]?.monitoring?.providers?.sentry?.dsn;
42
+ if (current === dsn) {
43
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(target)} DSN in place`);
44
+ continue;
45
+ }
46
+
47
+ edits[`targets.${target}.monitoring.providers.sentry.dsn`] = dsn;
48
+ console.log(` ${chalk.yellow('↻')} ${chalk.cyan(target)} DSN ${current ? 'drifted — updating' : 'landing'}`);
49
+ }
50
+
51
+ writeBrandConfig(context, edits);
52
+
53
+ return { output: { dsn: { landed: Object.keys(edits).length, targets: dsns } } };
54
+ };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Ensure the org, the brand's team, and one Sentry project per enabled
3
+ * target. Org resolution: monitoring.providers.sentry.org from config wins;
4
+ * otherwise the token's single org is used and written back there
5
+ * (self-heal), and a token seeing SEVERAL orgs is asked which one (#635). The team is the brand id; projects are `{brand.id}-{target}`
6
+ * with the target's Sentry platform. Existing projects (matched by slug)
7
+ * are converged proof — nothing is renamed or deleted here.
8
+ */
9
+ const chalk = require('chalk').default;
10
+ const { dryRunPlan } = require('../../../lib/run-gates.js');
11
+ const { writeBrandConfig } = require('../../../lib/config-write.js');
12
+ const { resolveConfigValue } = require('../../../lib/config-flow.js');
13
+
14
+ // Sentry platform slugs per target type — validated server-side against the
15
+ // integration-docs index (Project.is_valid_platform), so only ids that exist
16
+ // there work: Electron's is the top-level 'electron', NOT the frontend-UI
17
+ // alias 'javascript-electron' (400 Invalid platform — cp136b, live find).
18
+ // Unlisted target types are not monitored.
19
+ const TARGET_PLATFORMS = {
20
+ web: 'javascript',
21
+ backend: 'node',
22
+ desktop: 'electron',
23
+ extension: 'javascript',
24
+ };
25
+
26
+ module.exports = async function ensureProjects(context) {
27
+ const { sentryApi: api, brandConfig, options = {} } = context;
28
+ const brandId = brandConfig.brand.id;
29
+
30
+ // === Resolve the org (config wins; a lone token org self-heals into config) ===
31
+ const orgs = await api.getOrganizations();
32
+ const configured = brandConfig.monitoring.providers.sentry.org;
33
+ let org = configured
34
+ ? orgs.find((candidate) => candidate.slug === configured)
35
+ : (orgs.length === 1 ? orgs[0] : null);
36
+
37
+ // Ambiguous: the token sees several orgs and config names none. That is a
38
+ // question, not a warning (#635) — the list IS the answer, so ASK it. Zero
39
+ // orgs offers no choice, and a configured-but-invisible org is a different
40
+ // problem, so both keep their warn below. The service already gated on its
41
+ // token, hence `gate: false`.
42
+ if (!org && !configured && orgs.length > 0) {
43
+ const picked = await resolveConfigValue(context, {
44
+ path: 'monitoring.providers.sentry.org',
45
+ label: 'Sentry organization',
46
+ choices: async () => orgs,
47
+ getName: (candidate) => candidate.slug,
48
+ getValue: (candidate) => candidate.slug,
49
+ disablePath: 'monitoring.enabled',
50
+ gate: false,
51
+ });
52
+ org = picked ? orgs.find((candidate) => candidate.slug === picked) : null;
53
+ }
54
+
55
+ if (!org) {
56
+ if (configured) {
57
+ console.log(` ${chalk.yellow('⚠')} monitoring.providers.sentry.org '${configured}' is not visible to this token ${chalk.dim(`(it sees: ${orgs.map((candidate) => candidate.slug).join(', ') || 'none'})`)}`);
58
+ } else {
59
+ console.log(` ${chalk.yellow('⚠')} The token sees ${orgs.length} orgs — set monitoring.providers.sentry.org in omega.json5 to pick one`);
60
+ }
61
+ return { status: 'warned', reason: 'no Sentry org resolved — set monitoring.providers.sentry.org', output: { projects: { orgUnresolved: true } } };
62
+ }
63
+
64
+ // Multi-region SaaS: all org-scoped calls go to the org's home region
65
+ api.setRegionUrl(org.links?.regionUrl);
66
+
67
+ // Self-heal — skipped when the ask above already landed the pick (it patches
68
+ // the in-memory config too, so this reads the CURRENT state, not `configured`)
69
+ if (!brandConfig.monitoring.providers.sentry.org) {
70
+ writeBrandConfig(context, { 'monitoring.providers.sentry.org': org.slug });
71
+ }
72
+
73
+ const targets = Object.keys(brandConfig.targets || {}).filter((target) => TARGET_PLATFORMS[target]);
74
+ if (targets.length === 0) {
75
+ console.log(` ${chalk.dim('⊘ No monitorable targets enabled')}`);
76
+ return { state: { org: org.slug, projectMap: {} } };
77
+ }
78
+
79
+ const existing = await api.getProjects(org.slug);
80
+
81
+ const projects = {};
82
+ const planned = [];
83
+ let created = 0;
84
+ let synced = 0;
85
+ let teamEnsured = false;
86
+
87
+ for (const target of targets) {
88
+ const slug = `${brandId}-${target}`;
89
+ console.log(` ${chalk.dim('•')} ${chalk.cyan(slug)} ${chalk.dim(`(${TARGET_PLATFORMS[target]})`)}`);
90
+
91
+ const match = existing.find((project) => project.slug === slug);
92
+ if (match) {
93
+ console.log(` ${chalk.green('✓')} Exists`);
94
+ projects[target] = { id: match.id, slug: match.slug };
95
+ synced++;
96
+ continue;
97
+ }
98
+
99
+ if (options.dryRun) {
100
+ dryRunPlan(`create project ${slug}`);
101
+ planned.push(slug);
102
+ continue;
103
+ }
104
+
105
+ // The brand's team carries its projects — ensured once, on first need
106
+ if (!teamEnsured) {
107
+ const teams = await api.getTeams(org.slug);
108
+ if (!teams.find((team) => team.slug === brandId)) {
109
+ await api.createTeam(org.slug, brandId);
110
+ console.log(` ${chalk.green('✓')} Created team ${chalk.cyan(brandId)}`);
111
+ }
112
+ teamEnsured = true;
113
+ }
114
+
115
+ const project = await api.createProject(org.slug, brandId, {
116
+ name: slug,
117
+ slug,
118
+ platform: TARGET_PLATFORMS[target],
119
+ });
120
+ console.log(` ${chalk.green('✓')} Created`);
121
+ projects[target] = { id: project.id, slug: project.slug };
122
+ created++;
123
+ }
124
+
125
+ const summary = planned.length > 0
126
+ ? { synced, planned }
127
+ : { synced, created };
128
+
129
+ // State key is projectMap (certificateMap-style) — the output summary key
130
+ // `projects` would clobber it in the merged serviceData the dsn op reads.
131
+ return {
132
+ state: { org: org.slug, projectMap: projects },
133
+ output: { projects: summary },
134
+ };
135
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Monitoring service (Sentry provider) — one error-monitoring project per enabled target
3
+ * (web/backend/desktop/extension), with each project's DSN landed in
4
+ * targets.<type>.monitoring.providers.sentry.dsn via the comment-preserving
5
+ * writeback — the exact key every framework's runtime reads through the config
6
+ * merge chain. DSNs are public by design (schema-pinned); only the auth token
7
+ * is a secret.
8
+ *
9
+ * Gates: `monitoring.providers.sentry` present (the monitor is a KEY under
10
+ * `providers` since #425 — no entry means none chosen and this service skips,
11
+ * exactly what a null provider meant) + SENTRY_AUTH_TOKEN in the brand .env —
12
+ * a PERSONAL auth token with org:read, project:read, project:write, team:read,
13
+ * team:write (Sentry's "organization tokens" are CI-scoped and cannot create
14
+ * teams or projects, cp136). The projects operation resolves the org (config
15
+ * wins, else the token's lone visible org) and writes it back to
16
+ * monitoring.providers.sentry.org.
17
+ */
18
+ const { chosenProvider } = require('../../vendor/config/index.js');
19
+
20
+ const { serviceInputSpec } = require('../../config.js');
21
+ const { createServiceRunner } = require('../../lib/service-runner.js');
22
+ const { requestServiceInput } = require('../../lib/service-input.js');
23
+ const { SentryAPI } = require('./lib/sentry-api.js');
24
+
25
+ module.exports.run = createServiceRunner({
26
+ serviceDir: __dirname,
27
+ setup: async (context) => {
28
+ const monitoring = context.brandConfig.monitoring;
29
+
30
+ if (monitoring === false || monitoring?.enabled === false) {
31
+ return { skip: true, reason: 'monitoring disabled' };
32
+ }
33
+ if (!monitoring) {
34
+ return { skip: true, reason: 'no monitoring config' };
35
+ }
36
+
37
+ const provider = chosenProvider(monitoring.providers);
38
+ if (!provider) {
39
+ return { skip: true, reason: 'no monitoring.providers entry' };
40
+ }
41
+ if (provider !== 'sentry') {
42
+ return { skip: true, reason: `monitoring.providers.${provider} is not a known monitor` };
43
+ }
44
+
45
+ if (!context.sentryApi) {
46
+ const gate = await requestServiceInput(context, serviceInputSpec('monitoring'));
47
+ if (gate) return gate;
48
+ }
49
+
50
+ return {
51
+ sentryApi: context.sentryApi || new SentryAPI(),
52
+ };
53
+ },
54
+ });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Sentry API client (api/0) — organizations, teams, projects, client keys.
3
+ * Named-method surface so tests can fake it method-for-method.
4
+ *
5
+ * Auth: SENTRY_AUTH_TOKEN in the brand .env (a PERSONAL auth token with
6
+ * project+team write scopes — org tokens are CI-scoped and cannot create
7
+ * teams/projects, cp136). Multi-region SaaS: every org carries
8
+ * links.regionUrl; setRegionUrl() repoints the client so all org-scoped
9
+ * calls hit the org's home region.
10
+ */
11
+ const SENTRY_API_BASE = 'https://sentry.io/api/0';
12
+
13
+ class SentryAPI {
14
+ constructor(options = {}) {
15
+ this.authToken = options.authToken || process.env.SENTRY_AUTH_TOKEN;
16
+ this.apiBase = SENTRY_API_BASE;
17
+ }
18
+
19
+ /** Repoint org-scoped calls at the org's home region (multi-region SaaS). */
20
+ setRegionUrl(regionUrl) {
21
+ if (regionUrl) {
22
+ this.apiBase = `${regionUrl.replace(/\/$/, '')}/api/0`;
23
+ }
24
+ }
25
+
26
+ async makeRequest(endpoint, options = {}) {
27
+ const response = await fetch(`${this.apiBase}${endpoint}`, {
28
+ ...options,
29
+ headers: {
30
+ 'Authorization': `Bearer ${this.authToken}`,
31
+ 'Content-Type': 'application/json',
32
+ ...options.headers,
33
+ },
34
+ });
35
+
36
+ if (response.status === 204) {
37
+ return null;
38
+ }
39
+
40
+ const text = await response.text();
41
+ const data = text ? JSON.parse(text) : null;
42
+
43
+ if (!response.ok) {
44
+ // DRF validation errors have no detail/message — they're field-keyed
45
+ // ({"platform": ["Invalid platform"]}); serialize so the failure names
46
+ // its field instead of reading as a bare statusText.
47
+ const message = data?.detail
48
+ || data?.message
49
+ || (data ? JSON.stringify(data) : response.statusText);
50
+ throw new Error(`Sentry API error (${response.status}): ${message}`);
51
+ }
52
+
53
+ return data;
54
+ }
55
+
56
+ /** Organizations the token can see (an org token sees exactly one). */
57
+ async getOrganizations() {
58
+ return this.makeRequest('/organizations/');
59
+ }
60
+
61
+ /** Teams in the organization. */
62
+ async getTeams(orgSlug) {
63
+ return this.makeRequest(`/organizations/${orgSlug}/teams/`);
64
+ }
65
+
66
+ async createTeam(orgSlug, slug) {
67
+ return this.makeRequest(`/organizations/${orgSlug}/teams/`, {
68
+ method: 'POST',
69
+ body: JSON.stringify({ slug }),
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Projects in the organization (first 100 — slugs are matched exactly,
75
+ * and a colliding create on a beyond-page project 409s honestly).
76
+ */
77
+ async getProjects(orgSlug) {
78
+ return this.makeRequest(`/organizations/${orgSlug}/projects/?per_page=100`);
79
+ }
80
+
81
+ async createProject(orgSlug, teamSlug, { name, slug, platform }) {
82
+ return this.makeRequest(`/teams/${orgSlug}/${teamSlug}/projects/`, {
83
+ method: 'POST',
84
+ body: JSON.stringify({ name, slug, platform }),
85
+ });
86
+ }
87
+
88
+ /** Client keys for a project (a fresh project always has one). */
89
+ async getProjectKeys(orgSlug, projectSlug) {
90
+ return this.makeRequest(`/projects/${orgSlug}/${projectSlug}/keys/`);
91
+ }
92
+ }
93
+
94
+ module.exports = { SentryAPI, SENTRY_API_BASE };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Ensure @omega.js/backend's custom fields exist on the Beehiiv publication with the
3
+ * right kinds.
4
+ *
5
+ * The field list comes from @omega.js/backend's marketing SSOT (honoring each
6
+ * field's provider skip list — Beehiiv needs first/last name as custom
7
+ * fields, but tracks country and UTM source natively). Beehiiv matches
8
+ * subscriber values by display name, so fields are diffed by `display`. A
9
+ * kind mismatch can't be patched — the field is deleted and recreated.
10
+ * Fields @omega.js/backend doesn't own are never touched.
11
+ */
12
+ const chalk = require('chalk').default;
13
+ const { fieldsFor } = require('../../../lib/backend-marketing.js');
14
+ const { dryRunPlan } = require('../../../lib/run-gates.js');
15
+
16
+ const BEEHIIV_FIELDS = fieldsFor('beehiiv');
17
+
18
+ // @omega.js/backend type → Beehiiv kind
19
+ const KIND_MAP = { text: 'string', number: 'integer', date: 'datetime' };
20
+
21
+ module.exports = async function ensureCustomFields(context) {
22
+ const { beehiivApi: api, serviceData, options = {} } = context;
23
+
24
+ const publicationId = serviceData.publicationId;
25
+ if (!publicationId) {
26
+ console.log(chalk.dim(' ⊘ No publication yet — nothing to provision fields on'));
27
+ return {};
28
+ }
29
+
30
+ const existing = await api.getCustomFields(publicationId);
31
+ const existingByDisplay = Object.fromEntries(existing.map((f) => [f.display, f]));
32
+
33
+ const missing = [];
34
+ const mismatched = [];
35
+
36
+ for (const field of BEEHIIV_FIELDS) {
37
+ const bhField = existingByDisplay[field.display];
38
+
39
+ if (!bhField) {
40
+ missing.push(field);
41
+ } else if (bhField.kind !== KIND_MAP[field.type]) {
42
+ mismatched.push({ ...field, existingId: bhField.id, existingKind: bhField.kind });
43
+ }
44
+ }
45
+
46
+ if (missing.length === 0 && mismatched.length === 0) {
47
+ console.log(` ${chalk.green('✓')} All ${BEEHIIV_FIELDS.length} custom fields exist`);
48
+ return { output: { customFields: { total: BEEHIIV_FIELDS.length, created: 0, recreated: 0 } } };
49
+ }
50
+
51
+ if (options.dryRun) {
52
+ const planned = {
53
+ create: missing.map((f) => f.display),
54
+ recreate: mismatched.map((f) => `${f.display} (${f.existingKind} → ${KIND_MAP[f.type]})`),
55
+ };
56
+ return dryRunPlan(`create ${planned.create.length}, recreate ${planned.recreate.length} field(s)`, { output: { customFields: { planned } } });
57
+ }
58
+
59
+ // Kind mismatches: delete, then recreate alongside the missing ones
60
+ for (const field of mismatched) {
61
+ await api.deleteCustomField(publicationId, field.existingId);
62
+ console.log(` ${chalk.yellow('↻')} Deleted ${chalk.cyan(field.display)} ${chalk.dim(`(was ${field.existingKind}, need ${KIND_MAP[field.type]})`)}`);
63
+ missing.push(field);
64
+ }
65
+
66
+ for (const field of missing) {
67
+ await api.createCustomField(publicationId, field.name, field.display, KIND_MAP[field.type]);
68
+ console.log(` ${chalk.green('✓')} Created ${chalk.cyan(field.display)}`);
69
+ }
70
+
71
+ // Verify everything actually persisted
72
+ const verify = await api.getCustomFields(publicationId);
73
+ const verifyByDisplay = new Set(verify.map((f) => f.display));
74
+ const stillMissing = BEEHIIV_FIELDS.filter((f) => !verifyByDisplay.has(f.display));
75
+
76
+ if (stillMissing.length > 0) {
77
+ console.log(` ${chalk.yellow('⚠')} ${stillMissing.length} field(s) failed to persist: ${chalk.cyan(stillMissing.map((f) => f.display).join(', '))}`);
78
+ return { status: 'warned', reason: `${stillMissing.length} field(s) failed to persist: ${stillMissing.map((f) => f.display).join(', ')}`, output: { customFields: { total: BEEHIIV_FIELDS.length, failed: stillMissing.map((f) => f.display) } } };
79
+ }
80
+
81
+ return {
82
+ output: {
83
+ customFields: {
84
+ total: BEEHIIV_FIELDS.length,
85
+ created: missing.length - mismatched.length,
86
+ recreated: mismatched.length,
87
+ },
88
+ },
89
+ };
90
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Ensure the brand's Beehiiv publication is configured and accessible.
3
+ *
4
+ * Resolution: marketing.newsletter.providers.beehiiv.publicationId from
5
+ * config → auto-match by brand name/id across the account's publications.
6
+ * Publications can't be created via API — when nothing matches, the exact
7
+ * values to copy into the dashboard are printed, and interactive runs open
8
+ * the create page and poll until the new publication auto-matches;
9
+ * non-interactive/dry runs warn instead. The resolved id is written back
10
+ * into omega.json5 (marketing.newsletter.providers.beehiiv.publicationId —
11
+ * comment-preserving), its ONE authoritative home.
12
+ */
13
+ const chalk = require('chalk').default;
14
+ const { openBrowserAndPoll } = require('../../../vendor/devkit/flows.js');
15
+ const { writeBrandConfig } = require('../../../lib/config-write.js');
16
+ const { canPrompt } = require('../../../lib/run-gates.js');
17
+
18
+ const CREATE_URL = 'https://app.beehiiv.com/settings/workspace/overview?create_publication=true';
19
+
20
+ module.exports = async function ensurePublication(context) {
21
+ const { beehiivApi: api, brandConfig } = context;
22
+
23
+ const brandName = brandConfig.brand?.name || '';
24
+ const brandId = brandConfig.brand?.id || '';
25
+ const configuredId = brandConfig.marketing?.newsletter?.providers?.beehiiv?.publicationId;
26
+
27
+ // 1. Configured id — verify access
28
+ if (configuredId) {
29
+ const publication = await api.getPublication(configuredId);
30
+
31
+ if (!publication) {
32
+ console.log(` ${chalk.yellow('⚠')} Publication ${chalk.cyan(configuredId)} is not accessible with this API key — fix the id or the key, then rerun`);
33
+ return { status: 'warned', reason: 'the configured publication is not accessible with this API key', output: { publication: { publicationId: configuredId, accessible: false } } };
34
+ }
35
+
36
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(publication.name)} ${chalk.dim(`(${publication.id})`)}`);
37
+ if (publication.id !== configuredId) {
38
+ writeBrandConfig(context, { 'marketing.newsletter.providers.beehiiv.publicationId': publication.id });
39
+ }
40
+ return { state: { publicationId: publication.id, publicationName: publication.name } };
41
+ }
42
+
43
+ // 2. Auto-match by brand name/id
44
+ const nameLower = brandName.toLowerCase();
45
+ const idLower = brandId.toLowerCase();
46
+ const matchesBrand = (pub) => {
47
+ const pubName = (pub.name || '').toLowerCase();
48
+ return pubName.includes(nameLower)
49
+ || pubName.includes(idLower)
50
+ || nameLower.includes(pubName)
51
+ || idLower.includes(pubName.replace(/\s+/g, '-'));
52
+ };
53
+
54
+ const publications = await api.listPublications();
55
+ const match = publications.find(matchesBrand);
56
+
57
+ if (match) {
58
+ console.log(` ${chalk.green('✓')} Auto-matched ${chalk.cyan(match.name)} ${chalk.dim(`(${match.id})`)}`);
59
+ writeBrandConfig(context, { 'marketing.newsletter.providers.beehiiv.publicationId': match.id });
60
+ return { state: { publicationId: match.id, publicationName: match.name } };
61
+ }
62
+
63
+ // 3. Nothing matches — publications are dashboard-only, print the values to copy
64
+ console.log(` ${chalk.yellow('⚠')} No publication matches ${chalk.cyan(brandName)} — create one at ${chalk.cyan(CREATE_URL)}:`);
65
+ console.log(` ${chalk.dim('Publication name:')} ${chalk.cyan(brandName)}`);
66
+ console.log(` ${chalk.dim('This publication is...:')} ${chalk.cyan(brandConfig.brand?.description || `News and updates from ${brandName}`)}`);
67
+ console.log(` ${chalk.dim('Subdomain:')} ${chalk.cyan(brandId)}`);
68
+
69
+ // Interactive runs: open the dashboard and poll until the publication
70
+ // appears (same auto-match), then land it in omega.json5
71
+ if (canPrompt(context.options)) {
72
+ const result = await openBrowserAndPoll({
73
+ url: CREATE_URL,
74
+ label: 'the Beehiiv workspace settings',
75
+ promptMessage: `Create the publication for ${chalk.cyan(brandName)} with the values above.`,
76
+ waitMessage: 'Waiting for the publication to appear',
77
+ check: async () => {
78
+ const fresh = (await api.listPublications()).find(matchesBrand);
79
+ return fresh ? { done: true, result: fresh } : { done: false };
80
+ },
81
+ intervalMs: 10000,
82
+ indent: ' ',
83
+ });
84
+
85
+ if (result.success && result.result) {
86
+ const created = result.result;
87
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(created.name)} ${chalk.dim(`(${created.id})`)}`);
88
+ writeBrandConfig(context, { 'marketing.newsletter.providers.beehiiv.publicationId': created.id });
89
+ return { state: { publicationId: created.id, publicationName: created.name } };
90
+ }
91
+ }
92
+
93
+ return { status: 'warned', reason: 'no publication yet — create one in the beehiiv dashboard', output: { publication: { missing: true, suggestedName: brandName } } };
94
+ };