@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,467 @@
1
+ /**
2
+ * Health-check implementations for the testing service — omega-manager's
3
+ * target-checks helpers rebuilt for brand monorepos. Local checks read the
4
+ * repo; live checks fetch the deployed site/API and shell out to npm/gh.
5
+ *
6
+ * Every check records into a shared recorder whose results shape matches
7
+ * RunSummary's drill-down: passed = [name], warned = [{ name, warning }],
8
+ * failed = [{ name, error }]. Dim notes and dry-run "would" lines are
9
+ * display-only — they never enter the results.
10
+ *
11
+ * Injection seams (all via context.options — they ride runManage options so
12
+ * the full-loop tests stay network-free): options.fetch, options.exec,
13
+ * options.retryDelayMs.
14
+ */
15
+
16
+ const fs = require('node:fs');
17
+ const { execSync } = require('node:child_process');
18
+ const path = require('node:path');
19
+ const chalk = require('chalk').default;
20
+ const jetpack = require('fs-jetpack');
21
+
22
+ const { TARGET_FRAMEWORKS } = require('../../../config.js');
23
+ const { brandRepo, brandRepoName, instanceIdFromDirName, resolveInstanceUrl } = require('../../../vendor/config/index.js');
24
+ const { recordDeploy, readDeployRecord } = require('../../../vendor/devkit/deploy-record.js');
25
+
26
+ const MAX_RETRIES = 3;
27
+ const MAX_FILES_SHOWN = 10;
28
+
29
+ // Mirrors @omega.js/backend: getApiUrl() serves from the `api.` subdomain and
30
+ // mounts its routes under /omega (packages/backend route prefix)
31
+ const API_SUBDOMAIN = 'api';
32
+ const API_HEALTH_PATH = '/omega/health';
33
+
34
+ const FETCH_HEADERS = {
35
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
36
+ 'Pragma': 'no-cache',
37
+ 'User-Agent': 'OmegaManager/1.0 HealthCheck',
38
+ };
39
+
40
+ /**
41
+ * Create the shared check recorder. Prints each check as it lands and
42
+ * accumulates the results arrays the handler returns.
43
+ *
44
+ * @returns {Object} - { pass, warn, fail, note, would, results, counts }
45
+ */
46
+ function createRecorder() {
47
+ const passed = [];
48
+ const warned = [];
49
+ const failed = [];
50
+
51
+ return {
52
+ pass(name, detail) {
53
+ passed.push(name);
54
+ console.log(` ${chalk.green('✓')} ${name}${detail ? chalk.dim(`: ${detail}`) : ''}`);
55
+ },
56
+ warn(name, warning) {
57
+ warned.push({ name, warning });
58
+ console.log(` ${chalk.yellow('⚠')} ${name}${warning ? `: ${chalk.yellow(warning)}` : ''}`);
59
+ },
60
+ fail(name, error) {
61
+ failed.push({ name, error });
62
+ console.log(` ${chalk.red('✗')} ${name}${error ? `: ${chalk.red(error)}` : ''}`);
63
+ },
64
+ // Display-only informational line (check not applicable / not verifiable)
65
+ note(name, detail) {
66
+ console.log(` ${chalk.dim(`– ${name}${detail ? `: ${detail}` : ''}`)}`);
67
+ },
68
+ // Display-only dry-run line for a skipped remote action
69
+ would(action) {
70
+ console.log(` ${chalk.yellow('⊘')} would ${action} ${chalk.dim('[DRY RUN]')}`);
71
+ },
72
+ results() {
73
+ return { passed, warned, failed };
74
+ },
75
+ counts() {
76
+ return { passed: passed.length, warned: warned.length, failed: failed.length };
77
+ },
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Default exec — thin execSync wrapper so tests can inject a recording fake
83
+ * with the same (command, options) → stdout surface.
84
+ */
85
+ function defaultExec(command, options = {}) {
86
+ return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], ...options });
87
+ }
88
+
89
+ /**
90
+ * Fetch with retry — retries network errors AND non-2xx/3xx statuses with a
91
+ * linear backoff (delayMs, 2×delayMs, …), returning the last response either
92
+ * way. Success (2xx/3xx) returns immediately.
93
+ *
94
+ * @param {Object} ctx - Check context ({ fetchImpl, retryDelayMs })
95
+ * @param {string} url - URL to fetch
96
+ * @returns {Object} - { response, duration } or { error } after all retries
97
+ */
98
+ async function fetchWithRetry(ctx, url) {
99
+ let last = { error: 'unreachable' };
100
+
101
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
102
+ try {
103
+ const start = Date.now();
104
+ const response = await ctx.fetchImpl(url, { headers: FETCH_HEADERS, redirect: 'follow' });
105
+ last = { response, duration: Date.now() - start };
106
+
107
+ if (response.status >= 200 && response.status < 400) {
108
+ return last;
109
+ }
110
+ } catch (error) {
111
+ // Node's fetch wraps network errors ("fetch failed") — the cause has
112
+ // the useful message (getaddrinfo ENOTFOUND, ECONNREFUSED, …)
113
+ last = { error: error.cause?.message || error.message };
114
+ }
115
+
116
+ if (attempt < MAX_RETRIES) {
117
+ await new Promise((resolve) => setTimeout(resolve, ctx.retryDelayMs * attempt));
118
+ }
119
+ }
120
+
121
+ return last;
122
+ }
123
+
124
+ /**
125
+ * npm latest version of a package, cached per run. null when the registry
126
+ * lookup fails (unpublished package, offline) — callers dim out, never warn.
127
+ */
128
+ function getLatestVersion(ctx, packageName) {
129
+ if (!(packageName in ctx.versionCache)) {
130
+ try {
131
+ ctx.versionCache[packageName] = ctx.exec(`npm view ${packageName} version`).trim();
132
+ } catch {
133
+ ctx.versionCache[packageName] = null;
134
+ }
135
+ }
136
+ return ctx.versionCache[packageName];
137
+ }
138
+
139
+ /**
140
+ * Compare two x.y.z versions numerically. -1 / 0 / 1.
141
+ */
142
+ function compareVersions(a, b) {
143
+ const pa = a.split('.').map(Number);
144
+ const pb = b.split('.').map(Number);
145
+ for (let i = 0; i < 3; i++) {
146
+ if ((pa[i] || 0) < (pb[i] || 0)) return -1;
147
+ if ((pa[i] || 0) > (pb[i] || 0)) return 1;
148
+ }
149
+ return 0;
150
+ }
151
+
152
+ /**
153
+ * Resolve the INSTALLED version of a package via the node_modules climb from
154
+ * a directory — handles file:/workspace refs where the declared range says
155
+ * nothing. null when not installed.
156
+ */
157
+ function installedVersion(fromDir, packageName) {
158
+ let dir = path.resolve(fromDir);
159
+
160
+ while (true) {
161
+ const pkg = jetpack.read(path.join(dir, 'node_modules', packageName, 'package.json'), 'json');
162
+ if (pkg?.version) {
163
+ return pkg.version;
164
+ }
165
+
166
+ const parent = path.dirname(dir);
167
+ if (parent === dir) return null;
168
+ dir = parent;
169
+ }
170
+ }
171
+
172
+ // ── Local checks ────────────────────────────────────────────────────────────
173
+
174
+ /**
175
+ * Per-target file checks: package.json parses; web targets produced a build;
176
+ * backend targets have their Firebase scaffolding.
177
+ */
178
+ function checkTargetFiles(recorder, entry) {
179
+ const pkg = jetpack.read(path.join(entry.path, 'package.json'), 'json');
180
+ if (pkg) {
181
+ recorder.pass(`${entry.name}: package.json`);
182
+ } else {
183
+ recorder.fail(`${entry.name}: package.json`, 'missing or unparseable');
184
+ }
185
+
186
+ if (entry.target === 'web') {
187
+ if (fs.existsSync(path.join(entry.path, 'dist', 'index.html'))) {
188
+ recorder.pass(`${entry.name}: build output`);
189
+ } else {
190
+ recorder.fail(`${entry.name}: build output`, 'dist/index.html missing — run the update service');
191
+ }
192
+ }
193
+
194
+ if (entry.target === 'backend') {
195
+ // A custom-server backend (#584) deploys no Cloud Functions, so it has no
196
+ // firebase.json to carry — the ONE Firebase-only check here, noted rather
197
+ // than failed. The staged build below is checked in both modes.
198
+ if (entry.projectType === 'custom') {
199
+ recorder.note(`${entry.name}: firebase.json`, "skipped — projectType: 'custom' deploys no Cloud Functions");
200
+ } else if (fs.existsSync(path.join(entry.path, 'firebase.json'))) {
201
+ recorder.pass(`${entry.name}: firebase.json`);
202
+ } else {
203
+ recorder.fail(`${entry.name}: firebase.json`, 'missing');
204
+ }
205
+ // Staged output (src/dist pillar) — the backend's build-output twin of
206
+ // web's dist/index.html check
207
+ if (fs.existsSync(path.join(entry.path, 'dist', 'package.json'))) {
208
+ recorder.pass(`${entry.name}: staged dist/`);
209
+ } else {
210
+ recorder.fail(`${entry.name}: staged dist/`, 'dist/package.json missing — run the update service (omega build)');
211
+ }
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Framework version check: the target's installed framework package vs the npm
217
+ * latest. Silent when the target doesn't declare its framework;
218
+ * dims out when the registry lookup fails (e.g. package not published yet).
219
+ */
220
+ function checkFrameworkVersion(recorder, entry, ctx) {
221
+ const framework = TARGET_FRAMEWORKS[entry.target];
222
+ if (!framework) return;
223
+
224
+ // Deps live on the ONE manifest per target (src/dist pillar)
225
+ const pkgDir = entry.path;
226
+ const pkg = jetpack.read(path.join(pkgDir, 'package.json'), 'json');
227
+ const declared = pkg?.dependencies?.[framework] || pkg?.devDependencies?.[framework];
228
+ if (!declared) return;
229
+
230
+ const installed = installedVersion(pkgDir, framework) || declared.replace(/^[\^~>=<]+/, '');
231
+ const name = `${entry.name}: ${framework}`;
232
+
233
+ if (!/^\d/.test(installed)) {
234
+ recorder.note(name, `${installed} (not installed — run the update service)`);
235
+ return;
236
+ }
237
+
238
+ if (ctx.dryRun) {
239
+ recorder.would(`npm view ${framework} version`);
240
+ return;
241
+ }
242
+
243
+ const latest = getLatestVersion(ctx, framework);
244
+
245
+ if (!latest) {
246
+ recorder.note(name, `${installed} (could not check latest)`);
247
+ } else if (compareVersions(installed, latest) >= 0) {
248
+ recorder.pass(name, `${installed} (latest: ${latest})`);
249
+ } else {
250
+ recorder.warn(name, `outdated (${installed} → ${latest})`);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Parse `git status --porcelain` output into { status, file } entries.
256
+ * Lines must NOT be trimmed before slicing — the two status columns can
257
+ * legitimately start with a space (` M path`), so a leading trim shifts
258
+ * the path offset and eats the first character.
259
+ * @param {string} stdout - raw porcelain output
260
+ * @returns {Array<{ status: string, file: string }>}
261
+ */
262
+ function parseWorkingTree(stdout) {
263
+ return stdout.split('\n').filter((line) => line.trim()).map((line) => ({
264
+ status: line.substring(0, 2).trim(),
265
+ file: line.substring(3),
266
+ }));
267
+ }
268
+
269
+ /**
270
+ * Working tree check — uncommitted files under the brand root (scoped with
271
+ * `-- .` so a brand nested in a larger workspace only sees its own files).
272
+ * Silent when the brand isn't in a git repository.
273
+ */
274
+ function checkWorkingTree(recorder, ctx) {
275
+ let stdout;
276
+ try {
277
+ stdout = ctx.exec('git status --porcelain -- .', { cwd: ctx.brandRoot });
278
+ } catch {
279
+ return;
280
+ }
281
+
282
+ const files = parseWorkingTree(stdout);
283
+
284
+ if (files.length === 0) {
285
+ recorder.pass('working tree', 'clean');
286
+ return;
287
+ }
288
+
289
+ recorder.warn('working tree', `${files.length} uncommitted file${files.length === 1 ? '' : 's'}`);
290
+ for (const { status, file } of files.slice(0, MAX_FILES_SHOWN)) {
291
+ console.log(` ${chalk.dim(status)} ${chalk.dim(file)}`);
292
+ }
293
+ if (files.length > MAX_FILES_SHOWN) {
294
+ console.log(` ${chalk.dim(`... and ${files.length - MAX_FILES_SHOWN} more`)}`);
295
+ }
296
+ }
297
+
298
+ // ── Live checks ─────────────────────────────────────────────────────────────
299
+
300
+ /**
301
+ * Homepage check — the deployed site answers on ITS instance's URL: the
302
+ * instance entry's url (multi-instance targets), else the id AS a subdomain of
303
+ * the brand host (#588), falling back to the brand URL for the single-instance
304
+ * world. Deploy records key per instance too, so a never-deployed admin
305
+ * instance nudges without failing a live main.
306
+ */
307
+ async function checkHomepage(recorder, entry, ctx) {
308
+ const instance = instanceIdFromDirName(entry.name, entry.target);
309
+ const url = resolveInstanceUrl(ctx.brandConfig.targets?.[entry.target], instance, ctx.brandConfig);
310
+ const name = `${entry.name}: homepage`;
311
+
312
+ if (!url) {
313
+ recorder.warn(name, 'no brand.url configured — cannot check');
314
+ return;
315
+ }
316
+
317
+ if (ctx.dryRun) {
318
+ recorder.would(`fetch ${url}`);
319
+ return;
320
+ }
321
+
322
+ // No deploy record → a live miss means "not deployed yet", not an outage
323
+ // (never-deployed vs deployed-but-down — Ian 2026-07-17). A live HIT on a
324
+ // record-less brand adopts: the record is per-machine, so fresh clones of
325
+ // deployed brands self-heal here.
326
+ const deployed = readDeployRecord({ dir: ctx.brandRoot, target: entry.target, instance });
327
+ const { response, duration, error } = await fetchWithRetry(ctx, url);
328
+ const live = !error && response.status >= 200 && response.status < 400;
329
+
330
+ if (live) {
331
+ recorder.pass(name, `${url} → ${response.status} (${duration}ms)`);
332
+ if (!deployed) {
333
+ recordDeploy({ dir: ctx.brandRoot, target: entry.target, instance, detail: { adopted: true } });
334
+ }
335
+ } else if (!deployed) {
336
+ recorder.warn(name, `not deployed yet — run \`omega deploy\` when ready (${url})`);
337
+ } else {
338
+ recorder.fail(name, `${url} → ${error || response.status}`);
339
+ }
340
+ }
341
+
342
+ /**
343
+ * API health check — the deployed backend answers on
344
+ * https://api.{host}{API_HEALTH_PATH}, plus a deployed-version comparison
345
+ * from the health payload. Skipped for shared Firebase projects (the owning
346
+ * brand deploys the backend).
347
+ */
348
+ async function checkApiHealth(recorder, entry, ctx) {
349
+ const name = `${entry.name}: API health`;
350
+
351
+ if (ctx.brandConfig.cloud?.shared === true) {
352
+ recorder.note(name, 'shared Firebase project (owning brand deploys the backend)');
353
+ return;
354
+ }
355
+
356
+ const baseUrl = ctx.brandConfig.brand?.url;
357
+ if (!baseUrl) {
358
+ recorder.warn(name, 'no brand.url configured — cannot check');
359
+ return;
360
+ }
361
+
362
+ const host = baseUrl.replace(/^https?:\/\//, '');
363
+ const apiUrl = `https://${API_SUBDOMAIN}.${host}${API_HEALTH_PATH}`;
364
+
365
+ if (ctx.dryRun) {
366
+ recorder.would(`fetch ${apiUrl}`);
367
+ return;
368
+ }
369
+
370
+ // Same never-deployed vs deployed-but-down split as checkHomepage
371
+ const deployRecord = readDeployRecord({ dir: ctx.brandRoot, target: entry.target });
372
+ const { response, duration, error } = await fetchWithRetry(ctx, apiUrl);
373
+ const live = !error && response.status >= 200 && response.status < 400;
374
+
375
+ if (!live) {
376
+ if (!deployRecord) {
377
+ recorder.warn(name, `not deployed yet — run \`omega deploy\` when ready (${apiUrl})`);
378
+ } else {
379
+ recorder.fail(name, `${apiUrl} → ${error || response.status}`);
380
+ }
381
+ return;
382
+ }
383
+
384
+ recorder.pass(name, `${apiUrl} → ${response.status} (${duration}ms)`);
385
+ if (!deployRecord) {
386
+ recordDeploy({ dir: ctx.brandRoot, target: entry.target, detail: { adopted: true } });
387
+ }
388
+
389
+ // Deployed-version comparison from the health payload
390
+ let data = null;
391
+ try {
392
+ data = await response.json();
393
+ } catch {
394
+ return;
395
+ }
396
+
397
+ const framework = TARGET_FRAMEWORKS[entry.target];
398
+ const deployed = data?.backendVersion;
399
+ if (!deployed) return;
400
+
401
+ const deployedName = `${entry.name}: deployed backend`;
402
+ const latest = getLatestVersion(ctx, framework);
403
+
404
+ if (!latest) {
405
+ recorder.note(deployedName, `${deployed} (could not check latest)`);
406
+ } else if (deployed === latest) {
407
+ recorder.pass(deployedName, `${deployed} (up to date)`);
408
+ } else {
409
+ recorder.warn(deployedName, `outdated (${deployed} → ${latest})`);
410
+ }
411
+ }
412
+
413
+ /**
414
+ * GitHub Actions check — latest workflow run of the brand repo. Silent when
415
+ * no repo.providers.github.org is configured (the github service already reports that skip)
416
+ * or when the gh CLI is unavailable.
417
+ */
418
+ function checkGitHubActions(recorder, ctx) {
419
+ const github = ctx.brandConfig.repo?.providers?.github || {};
420
+ if (!github.org) return;
421
+
422
+ // BOTH halves from the one derivation (an `owner/name` slug owns itself); the
423
+ // service's own brand id stands in for a config that names no brand.
424
+ const repo = brandRepo(ctx.brandConfig).repo || `${github.org}/${brandRepoName({ brand: { id: ctx.brandId } })}`;
425
+
426
+ if (ctx.dryRun) {
427
+ recorder.would(`gh run list --repo ${repo}`);
428
+ return;
429
+ }
430
+
431
+ let runs;
432
+ try {
433
+ runs = JSON.parse(ctx.exec(`gh run list --repo ${repo} --limit 1 --json status,conclusion,name`));
434
+ } catch {
435
+ recorder.note('GitHub Actions', 'could not check (gh unavailable or no access)');
436
+ return;
437
+ }
438
+
439
+ if (!runs.length) {
440
+ recorder.note('GitHub Actions', 'no runs found');
441
+ return;
442
+ }
443
+
444
+ const run = runs[0];
445
+ if (run.status === 'completed' && run.conclusion === 'success') {
446
+ recorder.pass('GitHub Actions', `${run.name} → success`);
447
+ } else if (run.status === 'in_progress') {
448
+ recorder.warn('GitHub Actions', `${run.name} → in progress`);
449
+ } else {
450
+ recorder.fail('GitHub Actions', `${run.name} → ${run.conclusion || run.status}`);
451
+ }
452
+ }
453
+
454
+ module.exports = {
455
+ createRecorder,
456
+ defaultExec,
457
+ fetchWithRetry,
458
+ compareVersions,
459
+ installedVersion,
460
+ parseWorkingTree,
461
+ checkTargetFiles,
462
+ checkFrameworkVersion,
463
+ checkWorkingTree,
464
+ checkHomepage,
465
+ checkApiHealth,
466
+ checkGitHubActions,
467
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Update service — installs dependencies and builds every target in the brand
3
+ * monorepo. The monorepo cousin of omega-manager's update service (which
4
+ * walks separate per-target repos); phases beyond install/build (bump,
5
+ * deploy, sync) port over with their flags as the cutover advances.
6
+ */
7
+ const { createServiceRunner } = require('../../lib/service-runner.js');
8
+
9
+ module.exports.run = createServiceRunner({
10
+ serviceDir: __dirname,
11
+ setup: (context) => {
12
+ const targets = (context.targets || []).filter((entry) => entry.target);
13
+
14
+ if (targets.length === 0) {
15
+ return { skip: true, reason: 'no target-mapped dirs' };
16
+ }
17
+
18
+ return {};
19
+ },
20
+ });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The update service's incremental cache (#445) — `.omega/cache/update.json`
3
+ * at the brand root: per target, the two fingerprints its last successful build
4
+ * ran against. Derived data, so it lives with the brand's other caches under
5
+ * `.omega/cache/` (the edge service's read cache is its neighbour) — never in
6
+ * config, which holds provisioned facts.
7
+ *
8
+ * Missing, unparseable, or written in an older shape → an empty cache, which
9
+ * reads as "every target is fresh": the run does the full update and rewrites the
10
+ * file. A cache is an optimization, never a reason to fail the walk, so a
11
+ * failed write warns and the walk continues.
12
+ */
13
+ const { join } = require('node:path');
14
+ const chalk = require('chalk').default;
15
+ const jetpack = require('fs-jetpack');
16
+
17
+ const CACHE_VERSION = 2; // v2: the per-target map key renamed apps -> targets (#455)
18
+
19
+ /**
20
+ * @param {string} brandRoot - The brand monorepo root.
21
+ * @returns {string} Absolute path to the cache file.
22
+ */
23
+ function cacheFile(brandRoot) {
24
+ return join(brandRoot, '.omega', 'cache', 'update.json');
25
+ }
26
+
27
+ /**
28
+ * @param {string} brandRoot - The brand monorepo root.
29
+ * @returns {{ version: number, targets: Object }} The cache, or an empty one.
30
+ */
31
+ function readCache(brandRoot) {
32
+ let data = null;
33
+
34
+ try {
35
+ data = jetpack.read(cacheFile(brandRoot), 'json');
36
+ } catch {
37
+ data = null;
38
+ }
39
+
40
+ if (data?.version !== CACHE_VERSION || typeof data.targets !== 'object' || data.targets === null) {
41
+ return { version: CACHE_VERSION, targets: {} };
42
+ }
43
+
44
+ return data;
45
+ }
46
+
47
+ /**
48
+ * @param {string} brandRoot - The brand monorepo root.
49
+ * @param {Object} cache - The cache to persist.
50
+ */
51
+ function writeCache(brandRoot, cache) {
52
+ try {
53
+ jetpack.write(cacheFile(brandRoot), cache);
54
+ } catch (error) {
55
+ console.log(` ${chalk.yellow('⚠')} update cache not written${chalk.dim(`: ${error.message}`)}`);
56
+ }
57
+ }
58
+
59
+ module.exports = { readCache, writeCache, cacheFile, CACHE_VERSION };
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Update fingerprints (#445) — the two input sets that decide whether a target
3
+ * still needs its build, each hashed to one string:
4
+ *
5
+ * files — a sweep of the target's own tree (relative path + size + mtime
6
+ * per file): the assets service's mtime-diff precedent scaled
7
+ * from a file to a directory. Installed deps, build output,
8
+ * logs and the machinery dirs are excluded, so a build never
9
+ * re-dirties its own inputs. The merge-chain files ABOVE the
10
+ * target (brand and company omega.json5 + .env) fold in too —
11
+ * the build reads them, and a brand-config edit that left every
12
+ * target "converged" would serve stale output silently.
13
+ * frameworks — every declared `@omega.js/*` dependency's installed identity:
14
+ * the version string for an npm install, and for a LOCAL LINK
15
+ * (which points into the monorepo, where the version stands
16
+ * still between builds) the link target's version plus a sweep
17
+ * of its build output.
18
+ *
19
+ * Both are stat walks — no file contents are ever read.
20
+ */
21
+ const { createHash } = require('node:crypto');
22
+ const fs = require('node:fs');
23
+ const path = require('node:path');
24
+ const jetpack = require('fs-jetpack');
25
+
26
+ // Never part of a target's input set: installed deps (node_modules), build
27
+ // output (dist), run logs (logs), and the machinery `omega build` / `omega
28
+ // dev` / firebase write beside the sources (.temp, .omega, .cache, .firebase).
29
+ const EXCLUDED_DIRS = new Set(['node_modules', 'dist', 'logs', '.temp', '.omega', '.cache', '.firebase', '.git']);
30
+
31
+ /**
32
+ * Hash a directory tree by path + size + mtime. Entries are walked in name
33
+ * order so the digest is machine-independent; anything that is neither a
34
+ * plain file nor a directory (symlinks, sockets) is skipped, as are debug
35
+ * logs and .DS_Store — churn that says nothing about the sources.
36
+ *
37
+ * @param {string} dir - Directory to sweep.
38
+ * @returns {string|null} Hex digest, or null when the directory doesn't exist.
39
+ */
40
+ function sweep(dir) {
41
+ if (!jetpack.exists(dir)) {
42
+ return null;
43
+ }
44
+
45
+ const hash = createHash('sha1');
46
+
47
+ const walk = (current, prefix) => {
48
+ const entries = fs.readdirSync(current, { withFileTypes: true })
49
+ .sort((a, b) => (a.name < b.name ? -1 : 1));
50
+
51
+ for (const entry of entries) {
52
+ if (entry.isDirectory()) {
53
+ if (!EXCLUDED_DIRS.has(entry.name)) {
54
+ walk(path.join(current, entry.name), `${prefix}${entry.name}/`);
55
+ }
56
+ continue;
57
+ }
58
+
59
+ if (!entry.isFile() || entry.name.endsWith('.log') || entry.name === '.DS_Store') {
60
+ continue;
61
+ }
62
+
63
+ const stat = fs.statSync(path.join(current, entry.name));
64
+ hash.update(`${prefix}${entry.name}:${stat.size}:${stat.mtimeMs}\n`);
65
+ }
66
+ };
67
+
68
+ walk(dir, '');
69
+ return hash.digest('hex');
70
+ }
71
+
72
+ /**
73
+ * Where does `name` resolve from `fromDir` via the node_modules climb? A
74
+ * manual walk (not require.resolve) so exports-restricted packages and
75
+ * bin-only packages don't false-negative.
76
+ *
77
+ * @param {string} fromDir - Directory to climb from.
78
+ * @param {string} name - Package name.
79
+ * @returns {string|null} The package directory, or null when it doesn't resolve.
80
+ */
81
+ function resolvePackageDir(fromDir, name) {
82
+ let dir = path.resolve(fromDir);
83
+
84
+ while (true) {
85
+ const candidate = path.join(dir, 'node_modules', name);
86
+ if (fs.existsSync(candidate)) {
87
+ return candidate;
88
+ }
89
+
90
+ const parent = path.dirname(dir);
91
+ if (parent === dir) return null;
92
+ dir = parent;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Hash the installed identity of every `@omega.js/*` package the target declares.
98
+ *
99
+ * @param {string} targetPath - The target root.
100
+ * @returns {string} Hex digest.
101
+ */
102
+ function frameworkFingerprint(targetPath) {
103
+ const pkg = jetpack.read(path.join(targetPath, 'package.json'), 'json');
104
+ const declared = Object.keys({ ...pkg?.dependencies, ...pkg?.devDependencies })
105
+ .filter((name) => name.startsWith('@omega.js/'))
106
+ .sort();
107
+
108
+ const parts = declared.map((name) => {
109
+ const dir = resolvePackageDir(targetPath, name);
110
+ if (!dir) {
111
+ return `${name}@missing`;
112
+ }
113
+
114
+ const version = jetpack.read(path.join(dir, 'package.json'), 'json')?.version || 'unknown';
115
+ if (!fs.lstatSync(dir).isSymbolicLink()) {
116
+ return `${name}@${version}`;
117
+ }
118
+
119
+ // A local link resolves into the monorepo: the version never moves there,
120
+ // so the link target's build output is what actually changes under the target.
121
+ const target = fs.realpathSync(dir);
122
+ return `${name}@${version}#${sweep(path.join(target, 'dist')) || 'no-dist'}`;
123
+ });
124
+
125
+ return createHash('sha1').update(parts.join('\n')).digest('hex');
126
+ }
127
+
128
+ /**
129
+ * The pair of fingerprints cached per target.
130
+ *
131
+ * Chain entries are labelled (not path-keyed) so the digest survives a brand
132
+ * directory move the way the sweep's relative paths do; an absent file hashes
133
+ * as absent, so growing a .env later dirties the fingerprint like an edit.
134
+ *
135
+ * @param {string} targetPath - The target root.
136
+ * @param {Array<{ label: string, file: string }>} [chainFiles] - Merge-chain
137
+ * files outside the target tree that its build reads (brand/company config
138
+ * and .env).
139
+ * @returns {{ files: string, frameworks: string }}
140
+ */
141
+ function fingerprintTarget(targetPath, chainFiles = []) {
142
+ const chain = chainFiles.map(({ label, file }) => {
143
+ const stat = fs.statSync(file, { throwIfNoEntry: false });
144
+ return stat ? `${label}:${stat.size}:${stat.mtimeMs}` : `${label}:absent`;
145
+ });
146
+
147
+ const files = createHash('sha1')
148
+ .update([sweep(targetPath) || 'missing', ...chain].join('\n'))
149
+ .digest('hex');
150
+
151
+ return {
152
+ files,
153
+ frameworks: frameworkFingerprint(targetPath),
154
+ };
155
+ }
156
+
157
+ module.exports = { fingerprintTarget, resolvePackageDir, sweep, EXCLUDED_DIRS };