@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,445 @@
1
+ /**
2
+ * .env cascade — the secrets mirror of the omega.json5 hierarchy (D15).
3
+ *
4
+ * Weakest → strongest: company .env ← brand .env ← local .env ← shell.
5
+ *
6
+ * Same walk as the config cascade (load.js owns it): a target inside a brand
7
+ * monorepo ({brand}/targets/{target}) layers the brand root's .env under its own,
8
+ * and a brand stamped with .omega/company.json layers its company root's
9
+ * .env underneath that. Loading uses dotenv's no-override semantics — keys
10
+ * already in process.env (the shell) always win, and files apply
11
+ * innermost-first, so local beats brand beats company.
12
+ *
13
+ * Every layer is TWO files, not one
14
+ * ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)): its `.env` and
15
+ * the `.env.<environment>` overlay that wins over it — the widespread standard
16
+ * (Next.js, Vite, Rails dotenv, dotenv-flow). The environment names are exactly
17
+ * what envEnvironment() returns, so there is ONE vocabulary between the file
18
+ * name and the runtime's own answer, and only the RUNNING environment's overlay
19
+ * is ever read. Every key is equal: whatever the overlay holds wins, values are
20
+ * trusted, no key gets special treatment.
21
+ *
22
+ * Secrets are DEFINED once at their source level (a brand-wide GH_TOKEN in
23
+ * the brand .env, a company-wide key in the company .env) and RESOLVED here
24
+ * at runtime/build. Only a target that physically ships an env file still
25
+ * gets one materialized (dist/.env rides the Firebase deploy artifact —
26
+ * composeTargetEnv below builds it from this same chain, on every verb).
27
+ */
28
+
29
+ const fs = require('node:fs');
30
+ const path = require('node:path');
31
+
32
+ const { findBrandRoot } = require('./load.js');
33
+ const { readCompanyRoot } = require('./company.js');
34
+ const { ENV_SCHEMA, envFileGroups } = require('./env-schema.js');
35
+
36
+ // The ONE environment vocabulary, strongest signal first: every `.env.<name>`
37
+ // overlay is suffixed with one of these, every framework's environment() answers
38
+ // one of these, and nothing anywhere spells a fourth
39
+ // ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)).
40
+ const ENV_ENVIRONMENTS = ['development', 'testing', 'production'];
41
+
42
+ /**
43
+ * The runtime environment — the SINGLE SOURCE OF TRUTH for the one vocabulary,
44
+ * shared by the env overlay above and by every framework's own environment
45
+ * answer (@omega.js/backend's `env.environment()` / `Manager.getEnvironment()`
46
+ * delegate here). Exactly ONE of three mutually-exclusive values: testing wins,
47
+ * then production, else development.
48
+ *
49
+ * The final `else` is PRODUCTION on purpose: a deployed Cloud Function has no
50
+ * FUNCTIONS_EMULATOR and often no ENVIRONMENT var, so "no signal" IS the normal
51
+ * production state. Defaulting to development would make every deployed
52
+ * function skip real side effects (emails/analytics/webhooks). (Contrast
53
+ * UJM/BXM, whose deployed artifacts always carry their signal.)
54
+ *
55
+ * @returns {'testing'|'production'|'development'} The environment.
56
+ */
57
+ function envEnvironment() {
58
+ // Testing takes precedence — set by the test runner / emulator (OMEGA_TEST_MODE=true).
59
+ if (process.env.OMEGA_TEST_MODE === 'true') {
60
+ return 'testing';
61
+ }
62
+ if (process.env.ENVIRONMENT === 'production') {
63
+ return 'production';
64
+ } else if (
65
+ process.env.ENVIRONMENT === 'development'
66
+ || process.env.FUNCTIONS_EMULATOR === true
67
+ || process.env.FUNCTIONS_EMULATOR === 'true'
68
+ || process.env.TERM_PROGRAM === 'Apple_Terminal'
69
+ || process.env.TERM_PROGRAM === 'vscode'
70
+ ) {
71
+ return 'development';
72
+ } else {
73
+ return 'production';
74
+ }
75
+ }
76
+
77
+ /**
78
+ * The files ONE layer of the chain contributes, WEAKEST first: its `.env`, then
79
+ * the `.env.<environment>` overlay that wins over it (#586). A layer with no
80
+ * path contributes nothing; existence is not checked here.
81
+ *
82
+ * @param {string|null} envPath - The layer's base .env path.
83
+ * @param {string} [environment] - The running environment; absent = base only.
84
+ * @returns {string[]} Absolute paths, weakest first.
85
+ */
86
+ function envLayerFiles(envPath, environment) {
87
+ if (!envPath) return [];
88
+
89
+ return environment ? [envPath, `${envPath}.${environment}`] : [envPath];
90
+ }
91
+
92
+ /**
93
+ * Resolve the .env chain for a project dir, strongest file first.
94
+ *
95
+ * `startDir` is the dir whose .env is the local layer — the project root for
96
+ * web/desktop/extension, the functions dir for a backend (its .env rides
97
+ * the deploy artifact). Brand discovery normalizes a target subdir (functions/,
98
+ * dist/) → target root, same as the config loader.
99
+ *
100
+ * @param {string} startDir
101
+ * @returns {{ local: string, brand: string|null, company: string|null }}
102
+ * Absolute .env paths (existence not checked here).
103
+ */
104
+ function resolveEnvChain(startDir) {
105
+ const targetDir = path.resolve(startDir);
106
+ const brandRoot = findBrandRoot(targetDir);
107
+ // The marker sits at the brand root; when startDir IS a brand root (no
108
+ // targets/ walk above it), its own marker supplies the company layer.
109
+ const companyRoot = readCompanyRoot(brandRoot || targetDir);
110
+
111
+ return {
112
+ local: path.join(targetDir, '.env'),
113
+ brand: brandRoot ? path.join(brandRoot, '.env') : null,
114
+ company: companyRoot ? path.join(companyRoot, '.env') : null,
115
+ };
116
+ }
117
+
118
+ // Key OWNERSHIP, remembered instead of inferred
119
+ // ([#724](https://github.com/Omega-JS-Stack/omega/issues/724)). The no-override
120
+ // rule below is what makes the shell win, but after a boot load EVERY key is
121
+ // "already in process.env", so presence alone can no longer tell a shell value
122
+ // from a file value — which is why a reload used to have to skip edits. Two
123
+ // sets keep the answer: what process.env carried BEFORE this process read its
124
+ // first file (shell-owned, forever), and what a file layer has put there since
125
+ // (file-owned, the only keys reloadEnv may drop and re-read).
126
+ let shellOwnedKeys = null;
127
+ const fileOwnedKeys = new Set();
128
+
129
+ /**
130
+ * Record a key a file layer just delivered into process.env (#724).
131
+ *
132
+ * A key the shell brought stays shell-owned even if something deleted it and a
133
+ * file layer then supplied it — reloadEnv must never rewrite one.
134
+ *
135
+ * @param {string} key
136
+ */
137
+ function markFileOwned(key) {
138
+ if (!shellOwnedKeys.has(key)) fileOwnedKeys.add(key);
139
+ }
140
+
141
+ /**
142
+ * Load an ordered list of .env files (strongest first) with dotenv's
143
+ * no-override semantics: keys already in process.env (the shell, or a
144
+ * stronger file) always win, so load order = precedence. Null/missing
145
+ * entries skip silently.
146
+ *
147
+ * One rule on top of plain dotenv (dogfood friction #20): a file layer's
148
+ * EMPTY value (`KEY=` / `KEY=""`) never claims the key — empty means
149
+ * "documented here, value supplied by another layer", so a scaffolded
150
+ * local .env full of placeholders can't shadow the brand root's real
151
+ * values. Only the shell can deliberately set a key to empty.
152
+ *
153
+ * @param {Array<string|null>} envPaths
154
+ * @returns {string[]} The files that existed and were loaded.
155
+ */
156
+ function loadEnvChain(envPaths) {
157
+ // The first chain load in the process fixes the shell-owned set: nothing here
158
+ // has read a file yet, so whatever process.env carries came from outside (#724)
159
+ if (shellOwnedKeys === null) shellOwnedKeys = new Set(Object.keys(process.env));
160
+
161
+ const loaded = [];
162
+
163
+ for (const envPath of envPaths) {
164
+ if (!envPath || !fs.existsSync(envPath)) continue;
165
+
166
+ const parsed = require('dotenv').parse(fs.readFileSync(envPath, 'utf8'));
167
+ for (const [key, value] of Object.entries(parsed)) {
168
+ if (value === '' || key in process.env) continue;
169
+ process.env[key] = value;
170
+ markFileOwned(key);
171
+ }
172
+
173
+ loaded.push(envPath);
174
+ }
175
+
176
+ return loaded;
177
+ }
178
+
179
+ /**
180
+ * Load the .env cascade for a list of LAYER ROOTS, strongest root first: each
181
+ * root's `.env` plus the `.env.<environment>` overlay that wins over it (#586).
182
+ *
183
+ * The known-layers counterpart of loadEnv, which starts from a target dir and
184
+ * DISCOVERS its chain. The manager's walks already know theirs — the brand
185
+ * root, then the company root under it — and only need them loaded in order;
186
+ * a null root (a standalone brand's missing company layer) skips.
187
+ *
188
+ * @param {Array<string|null>} roots - Layer roots, strongest first.
189
+ * @param {object} [options]
190
+ * @param {string} [options.environment] - The environment whose overlay applies
191
+ * (defaults to the running one).
192
+ * @returns {string[]} The files that existed and were loaded.
193
+ */
194
+ function loadEnvRoots(roots, { environment = envEnvironment() } = {}) {
195
+ // Strongest first, so a layer's overlay is offered before its own base
196
+ return loadEnvChain(roots.flatMap((root) => envLayerFiles(root && path.join(root, '.env'), environment).reverse()));
197
+ }
198
+
199
+ /**
200
+ * Apply the schema's `deliverAs` renames to a value map, in place — the ONE
201
+ * place the rename lives ([#678](https://github.com/Omega-JS-Stack/omega/issues/678)).
202
+ *
203
+ * Per entry that carries `deliverAs` and names `target`: the brand-level name
204
+ * (`GOOGLE_ANALYTICS_SECRET_WEB`) becomes the runtime name
205
+ * (`GOOGLE_ANALYTICS_SECRET`) — a target only ever sees the one it reads, so
206
+ * the source name is consumed. A value already sitting under the delivered
207
+ * name WINS (the shell, or a human's explicit answer); the source name is
208
+ * consumed either way.
209
+ *
210
+ * Backend rides it through composeTargetEnv's dist/.env; every other target
211
+ * rides it through loadEnv, which delivers into process.env at CLI boot.
212
+ *
213
+ * @param {Object<string, string>} values - Mutated in place (a parsed layer, or process.env).
214
+ * @param {string} target - Target name ('backend', 'web', …).
215
+ * @returns {string[]} The delivered names that were set.
216
+ */
217
+ function applyDeliverAs(values, target) {
218
+ const delivered = [];
219
+
220
+ for (const entry of ENV_SCHEMA) {
221
+ if (!entry.deliverAs || !entry.targets.includes(target)) continue;
222
+
223
+ const value = values[entry.name];
224
+ delete values[entry.name];
225
+ if (!value) continue;
226
+ if (values[entry.deliverAs]) continue;
227
+
228
+ values[entry.deliverAs] = value;
229
+ delivered.push(entry.deliverAs);
230
+ }
231
+
232
+ return delivered;
233
+ }
234
+
235
+ /**
236
+ * Resolve + load the full .env cascade for a project dir:
237
+ * shell > local .env > brand .env > company .env, each layer's
238
+ * `.env.<environment>` overlay winning over its own `.env` (#586).
239
+ *
240
+ * Pass the caller's `target` and the schema's `deliverAs` renames land in
241
+ * process.env too — the web/desktop/extension half of the delivery the
242
+ * backend gets from composeTargetEnv's dist/.env. Without a target nothing is
243
+ * renamed.
244
+ *
245
+ * @param {string} startDir - See resolveEnvChain.
246
+ * @param {object} [options]
247
+ * @param {string} [options.target] - Target name ('web', 'desktop', …).
248
+ * @param {string} [options.environment] - The environment whose overlay applies
249
+ * (defaults to the running one).
250
+ * @returns {{ chain: { local: string, brand: string|null, company: string|null }, loaded: string[] }}
251
+ */
252
+ function loadEnv(startDir, { target, environment = envEnvironment() } = {}) {
253
+ const chain = resolveEnvChain(startDir);
254
+ // Strongest first, so a layer's overlay is offered before its own base
255
+ const files = ['local', 'brand', 'company']
256
+ .flatMap((layer) => envLayerFiles(chain[layer], environment).reverse());
257
+ const loaded = loadEnvChain(files);
258
+ // A delivered name INHERITS its source key's ownership (#724): it carries a
259
+ // file layer's value under a second key, so it is normally file-owned too —
260
+ // otherwise an edited source value could not reach it. But the rename CONSUMES
261
+ // the source key, so once the shell exported that source name, the delivered
262
+ // name is the only place the shell's value still lives; calling it file-owned
263
+ // would let a reload drop it (gone when no file declares the source, replaced
264
+ // by the file's value when one does).
265
+ if (target) {
266
+ const shellSourced = new Set(ENV_SCHEMA
267
+ .filter((entry) => entry.deliverAs && entry.targets.includes(target) && shellOwnedKeys.has(entry.name))
268
+ .map((entry) => entry.deliverAs));
269
+
270
+ for (const key of applyDeliverAs(process.env, target)) {
271
+ // Inheritance is the whole answer, so a shell-sourced delivery also CLEARS
272
+ // a file-owned mark an earlier load left on that name
273
+ if (shellSourced.has(key)) fileOwnedKeys.delete(key);
274
+ else markFileOwned(key);
275
+ }
276
+ }
277
+ return { chain, loaded };
278
+ }
279
+
280
+ /**
281
+ * Re-read the .env cascade for a project dir so EDITED file values land
282
+ * ([#724](https://github.com/Omega-JS-Stack/omega/issues/724)) — the reload
283
+ * half of loadEnv, same arguments, same answer.
284
+ *
285
+ * loadEnv alone cannot honor an edit: its no-override rule sees the key the
286
+ * boot load put there and skips it. So this DROPS every file-owned key first —
287
+ * the ones a file layer delivered, never one the shell brought — and then runs
288
+ * the same load. Consequences, all of them the file being re-read rather than
289
+ * merged onto the old set:
290
+ * - an EDITED value lands, and so does a NEW key;
291
+ * - a key DROPPED from the file is dropped from the process;
292
+ * - a SHELL-set key is untouched, whatever any file now says.
293
+ *
294
+ * It re-reads the chain it is GIVEN, so a process that loaded several projects'
295
+ * cascades keeps only the reloaded one's file keys. The dev lanes' `.env`
296
+ * watchers (#681) are the caller, and each watches its own single target.
297
+ *
298
+ * @param {string} startDir - See resolveEnvChain.
299
+ * @param {object} [options] - See loadEnv.
300
+ * @param {string} [options.target] - Target name ('web', 'desktop', …).
301
+ * @param {string} [options.environment] - The environment whose overlay applies.
302
+ * @returns {{ chain: { local: string, brand: string|null, company: string|null }, loaded: string[] }}
303
+ */
304
+ function reloadEnv(startDir, { target, environment = envEnvironment() } = {}) {
305
+ for (const key of fileOwnedKeys) delete process.env[key];
306
+ fileOwnedKeys.clear();
307
+
308
+ return loadEnv(startDir, { target, environment });
309
+ }
310
+
311
+ /**
312
+ * Parse one .env file into a plain map. Missing files read as empty.
313
+ *
314
+ * @param {string|null} envPath
315
+ * @returns {Object<string, string>} Parsed key → value.
316
+ */
317
+ function parseEnvFile(envPath) {
318
+ if (!envPath || !fs.existsSync(envPath)) return {};
319
+
320
+ return require('dotenv').parse(fs.readFileSync(envPath, 'utf8'));
321
+ }
322
+
323
+ /**
324
+ * The schema entry that DELIVERS `key` to `target`, if any — an exact name
325
+ * first, then the dynamic families' patterns (`envSchemaEntry`'s order). A key
326
+ * is delivered only when its entry names the target AND its group renders into
327
+ * a real file: the `file: false` groups are the backend's own resolution lanes
328
+ * (config at boot, the developer's shell), never something a brand hands down.
329
+ *
330
+ * @param {string} key - The env var name in the brand/company layer.
331
+ * @param {string} target - Target name ('backend', 'web', …).
332
+ * @returns {object|undefined} The entry, or undefined when the key stays home.
333
+ */
334
+ function deliveringEntry(key, target) {
335
+ const fileGroups = new Set(envFileGroups().map((group) => group.id));
336
+ const claims = (entry) => entry.targets.includes(target) && fileGroups.has(entry.group);
337
+
338
+ return ENV_SCHEMA.find((entry) => entry.name === key && claims(entry))
339
+ || ENV_SCHEMA.find((entry) => entry.match instanceof RegExp && entry.match.test(key) && claims(entry));
340
+ }
341
+
342
+ /**
343
+ * Compose the env a target's own artifact ships with
344
+ * ([#678](https://github.com/Omega-JS-Stack/omega/issues/678)).
345
+ *
346
+ * FILES ONLY, never the shell: company .env ← brand .env ← target .env, the
347
+ * same chain resolveEnvChain walks, strongest last. The brand root's .env is
348
+ * the ONE file humans and the manager edit; a target's own .env is an optional
349
+ * per-key override a human writes. Nothing here reads or writes process.env —
350
+ * a build must produce the same artifact under any shell.
351
+ *
352
+ * Each layer is its `.env` plus the `.env.<environment>` overlay that wins over
353
+ * it (#586), so ONE flat artifact ships for ONE environment: a deploy composes
354
+ * base + production, the emulator base + development, a test lane base + testing
355
+ * — and no other environment's file ever rides along.
356
+ *
357
+ * The two brand-side layers are FILTERED by the env schema (the only filter
358
+ * there is): a key rides down when some entry claims it — by name or by
359
+ * pattern — names this target, and sits in a file group. The TARGET layer
360
+ * passes through unfiltered: placing a key in the target's own .env IS the
361
+ * targeting. `deliverAs` renames on arrival in EVERY layer (the per-target GA4
362
+ * secrets), so a human writing the brand-level name in the target's own .env
363
+ * gets the one delivered key, overriding the brand's.
364
+ *
365
+ * An empty value never claims a key, the same rule loadEnvChain applies: empty
366
+ * means "documented here, valued by another layer".
367
+ *
368
+ * @param {object} options
369
+ * @param {string} options.targetDir - The target root (its .env is the local layer).
370
+ * @param {string} options.target - Target name ('backend', 'web', …).
371
+ * @param {string} [options.environment] - The environment whose overlay composes
372
+ * (defaults to the running one).
373
+ * @returns {{ values: Object<string, string>, sources: Object<string, string> }}
374
+ * `sources` maps each delivered key to the layer it came from
375
+ * (`company`/`brand`/`target`) — an overlay reports as its own layer, for
376
+ * logging by key NAME only.
377
+ */
378
+ function composeTargetEnv({ targetDir, target, environment = envEnvironment() }) {
379
+ const chain = resolveEnvChain(targetDir);
380
+ const values = {};
381
+ const sources = {};
382
+
383
+ const deliver = (key, value, layer) => {
384
+ if (!value) return;
385
+ values[key] = value;
386
+ sources[key] = layer;
387
+ };
388
+
389
+ // Weakest first — each file overwrites what the ones below it delivered, and
390
+ // a layer's overlay sits directly above its own base
391
+ const files = [
392
+ ...envLayerFiles(chain.company, environment).map((file) => ({ file, layer: 'company', filtered: true })),
393
+ ...envLayerFiles(chain.brand, environment).map((file) => ({ file, layer: 'brand', filtered: true })),
394
+ // The TARGET layer passes through unfiltered: placement IS the targeting
395
+ ...envLayerFiles(chain.local, environment).map((file) => ({ file, layer: 'target', filtered: false })),
396
+ ];
397
+
398
+ for (const { file, layer, filtered } of files) {
399
+ const claimed = {};
400
+ for (const [key, value] of Object.entries(parseEnvFile(file))) {
401
+ if (filtered && !deliveringEntry(key, target)) continue;
402
+ claimed[key] = value;
403
+ }
404
+
405
+ applyDeliverAs(claimed, target);
406
+ for (const [key, value] of Object.entries(claimed)) {
407
+ deliver(key, value, layer);
408
+ }
409
+ }
410
+
411
+ return { values, sources };
412
+ }
413
+
414
+ /**
415
+ * Serialize one value as a double-quoted .env line — the serializer SSOT every
416
+ * writeback rides (this composer, the manager's brand .env writeback and
417
+ * scaffold stub). Backslashes, quotes and newlines escape so a multi-line blob
418
+ * stays line-safe (dotenv expands `\n` back on read).
419
+ *
420
+ * @param {string} key - Env var name.
421
+ * @param {string} value - Value to serialize.
422
+ * @returns {string} `KEY="value"`.
423
+ */
424
+ function envLine(key, value) {
425
+ const escaped = String(value)
426
+ .replace(/\\/g, '\\\\')
427
+ .replace(/"/g, '\\"')
428
+ .replace(/\n/g, '\\n');
429
+
430
+ return `${key}="${escaped}"`;
431
+ }
432
+
433
+ /**
434
+ * Serialize composed values as .env content — one envLine per key.
435
+ *
436
+ * @param {Object<string, string>} values
437
+ * @returns {string} The file content, newline-terminated.
438
+ */
439
+ function serializeEnv(values) {
440
+ const lines = Object.entries(values).map(([key, value]) => envLine(key, value));
441
+
442
+ return `${lines.join('\n')}\n`;
443
+ }
444
+
445
+ module.exports = { loadEnv, reloadEnv, ENV_ENVIRONMENTS, envEnvironment, resolveEnvChain, envLayerFiles, loadEnvChain, loadEnvRoots, applyDeliverAs, composeTargetEnv, envLine, serializeEnv };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Company/brand hooks — owner-supplied code the frameworks call at named
3
+ * hook points, so company-specific logic (e.g. a password formula) lives in
4
+ * the OWNER'S tree, never in framework source.
5
+ *
6
+ * Layout is NESTED, mirroring the call site that invokes the hook (decided
7
+ * with Ian 2026-07-10): the account service's password step loads
8
+ * `config/hooks/account/password.js`, a future onboarding hook would live
9
+ * under `config/hooks/onboard/…` — one file per hook point, path = the
10
+ * invoking structure, never a flat name-mangled file.
11
+ *
12
+ * Hooks live under `config/` — the established home for owner-authored
13
+ * omega inputs (omega.json5, seo.json5, chatsy.md, …) — and are therefore
14
+ * VERSIONED by default (Ian 2026-07-11: hooks are authored code, and a
15
+ * gitignored hook lost on a fresh clone would silently change behavior —
16
+ * e.g. account passwords falling back to the seed channel). Never `.omega/`,
17
+ * which is machine-owned bookkeeping. Secrets still belong in .env — a hook
18
+ * that needs one reads process.env; owners who truly want a hook out of git
19
+ * add their own `config/hooks/` ignore line.
20
+ *
21
+ * Resolution walks the same hierarchy as the .env cascade: the brand root's
22
+ * own `config/hooks/` first, then the company root's (via the
23
+ * .omega/company.json stamp) — so a company-wide hook covers every brand and
24
+ * a single brand can still override it. Hooks are plain CJS modules whose
25
+ * `module.exports` IS the hook function; call-site docs define each hook's
26
+ * signature and return contract.
27
+ */
28
+
29
+ const fs = require('node:fs');
30
+ const path = require('node:path');
31
+
32
+ const { readCompanyRoot } = require('./company.js');
33
+
34
+ // Hook points are code-owned kebab-case path constants ('account/password') —
35
+ // enforce the shape so a typo'd or traversal-shaped path fails loudly.
36
+ const HOOK_PATH_PATTERN = /^[a-z0-9-]+(\/[a-z0-9-]+)*$/;
37
+
38
+ /**
39
+ * The candidate file for one hook point under one root.
40
+ */
41
+ function hookFile(root, hookPath) {
42
+ return `${path.join(root, 'config', 'hooks', ...hookPath.split('/'))}.js`;
43
+ }
44
+
45
+ /**
46
+ * Resolve a hook point to the file that defines it: the brand root's own
47
+ * `config/hooks/<hookPath>.js`, else the company root's (company.json stamp).
48
+ *
49
+ * @param {string} startRoot - Brand (or standalone-project) root
50
+ * @param {string} hookPath - Call-site-mirroring hook point, e.g. 'account/password'
51
+ * @returns {string|null} Absolute hook file path, or null when neither root defines it
52
+ */
53
+ function resolveHook(startRoot, hookPath) {
54
+ if (!HOOK_PATH_PATTERN.test(hookPath)) {
55
+ throw new Error(`Invalid hook path ${JSON.stringify(hookPath)} — kebab-case segments joined by '/', e.g. 'account/password'`);
56
+ }
57
+
58
+ const roots = [path.resolve(startRoot)];
59
+ const companyRoot = readCompanyRoot(roots[0]);
60
+ if (companyRoot && companyRoot !== roots[0]) {
61
+ roots.push(companyRoot);
62
+ }
63
+
64
+ return roots.map((root) => hookFile(root, hookPath)).find((file) => fs.existsSync(file)) || null;
65
+ }
66
+
67
+ /**
68
+ * Load a hook point's function. Absent hooks return null (callers fall
69
+ * through to their default behavior); a hook file that exists but is broken
70
+ * — unloadable, or not exporting a function — throws, because an owner who
71
+ * wrote a hook must never get silent fallback.
72
+ *
73
+ * @param {string} startRoot - Brand (or standalone-project) root
74
+ * @param {string} hookPath - Call-site-mirroring hook point, e.g. 'account/password'
75
+ * @returns {{ fn: Function, file: string }|null}
76
+ */
77
+ function loadHook(startRoot, hookPath) {
78
+ const file = resolveHook(startRoot, hookPath);
79
+ if (!file) return null;
80
+
81
+ let exported;
82
+ try {
83
+ exported = require(file);
84
+ } catch (e) {
85
+ throw new Error(`Hook ${hookPath} failed to load (${file}): ${e.message}`);
86
+ }
87
+
88
+ // CJS module.exports = fn is the contract; tolerate an ESM default export
89
+ const fn = typeof exported === 'function' ? exported : exported?.default;
90
+ if (typeof fn !== 'function') {
91
+ throw new Error(`Hook ${hookPath} (${file}) must export a function (module.exports = ({ … }) => …)`);
92
+ }
93
+
94
+ return { fn, file };
95
+ }
96
+
97
+ module.exports = { resolveHook, loadHook };