@omega.js/web 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 (1162) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +640 -0
  3. package/bin/omega +2 -0
  4. package/bin/omega-web +2 -0
  5. package/bin/omg +2 -0
  6. package/core/_includes/admin/sections/sidebar.json +100 -0
  7. package/core/_includes/admin/sections/topbar.json +11 -0
  8. package/core/_includes/backend/sections/sidebar.json +39 -0
  9. package/core/_includes/backend/sections/topbar.json +16 -0
  10. package/core/_includes/core/body.html +102 -0
  11. package/core/_includes/core/foot.html +536 -0
  12. package/core/_includes/core/head.html +423 -0
  13. package/core/_includes/core/pricing/price-per-unit.html +33 -0
  14. package/core/_includes/core/showcase/entry-body.html +94 -0
  15. package/core/_includes/core/showcase/index-body.html +43 -0
  16. package/core/_includes/core/showcase/nav.html +60 -0
  17. package/core/_includes/frontend/sections/footer.json +74 -0
  18. package/core/_includes/frontend/sections/nav.json +70 -0
  19. package/core/_includes/global/sections/account.json +31 -0
  20. package/core/_includes/modules/engagement/giscus.html +26 -0
  21. package/core/_layouts/blueprint/404.html +10 -0
  22. package/core/_layouts/blueprint/about.html +10 -0
  23. package/core/_layouts/blueprint/account/index.html +11 -0
  24. package/core/_layouts/blueprint/admin/calendar/index.html +590 -0
  25. package/core/_layouts/blueprint/admin/dashboard/index.html +301 -0
  26. package/core/_layouts/blueprint/admin/firebase/index.html +214 -0
  27. package/core/_layouts/blueprint/admin/posts/editor.html +157 -0
  28. package/core/_layouts/blueprint/admin/posts/index.html +126 -0
  29. package/core/_layouts/blueprint/admin/stackblitz/index.html +30 -0
  30. package/core/_layouts/blueprint/admin/studio/index.html +85 -0
  31. package/core/_layouts/blueprint/admin/users/index.html +272 -0
  32. package/core/_layouts/blueprint/admin/users/new.html +82 -0
  33. package/core/_layouts/blueprint/admin/verts/index.html +204 -0
  34. package/core/_layouts/blueprint/alternatives/alternative.html +15 -0
  35. package/core/_layouts/blueprint/alternatives/index.html +10 -0
  36. package/core/_layouts/blueprint/app.html +10 -0
  37. package/core/_layouts/blueprint/auth/reset.html +19 -0
  38. package/core/_layouts/blueprint/auth/signin.html +19 -0
  39. package/core/_layouts/blueprint/auth/signup.html +19 -0
  40. package/core/_layouts/blueprint/auth/token.html +19 -0
  41. package/core/_layouts/blueprint/blog/categories/category.html +8 -0
  42. package/core/_layouts/blueprint/blog/categories/index.html +11 -0
  43. package/core/_layouts/blueprint/blog/index.html +10 -0
  44. package/core/_layouts/blueprint/blog/post.html +5 -0
  45. package/core/_layouts/blueprint/blog/tags/index.html +11 -0
  46. package/core/_layouts/blueprint/blog/tags/tag.html +8 -0
  47. package/core/_layouts/blueprint/careers.md +13 -0
  48. package/core/_layouts/blueprint/collection/category.html +9 -0
  49. package/core/_layouts/blueprint/collection/document.html +9 -0
  50. package/core/_layouts/blueprint/collection/index.html +9 -0
  51. package/core/_layouts/blueprint/connections/callback.html +11 -0
  52. package/core/_layouts/blueprint/contact.html +14 -0
  53. package/core/_layouts/blueprint/dashboard/index.html +10 -0
  54. package/core/_layouts/blueprint/download.html +20 -0
  55. package/core/_layouts/blueprint/extension/index.html +14 -0
  56. package/core/_layouts/blueprint/extension/installed.html +11 -0
  57. package/core/_layouts/blueprint/feedback.html +10 -0
  58. package/core/_layouts/blueprint/index.html +16 -0
  59. package/core/_layouts/blueprint/legal/cookies.md +57 -0
  60. package/core/_layouts/blueprint/legal/privacy.md +109 -0
  61. package/core/_layouts/blueprint/legal/terms.md +155 -0
  62. package/core/_layouts/blueprint/payment/checkout.html +15 -0
  63. package/core/_layouts/blueprint/payment/confirmation.html +14 -0
  64. package/core/_layouts/blueprint/portal/email-preferences.html +15 -0
  65. package/core/_layouts/blueprint/pricing.html +20 -0
  66. package/core/_layouts/blueprint/status.html +10 -0
  67. package/core/_layouts/blueprint/team/index.html +10 -0
  68. package/core/_layouts/blueprint/team/member.html +5 -0
  69. package/core/_layouts/blueprint/updates/index.html +10 -0
  70. package/core/_layouts/blueprint/updates/update.html +10 -0
  71. package/core/_layouts/core/root.html +45 -0
  72. package/core/_layouts/modules/utilities/redirect.html +129 -0
  73. package/core/css/_studio.scss +237 -0
  74. package/core/css/components/_index.scss +146 -0
  75. package/core/css/core/_.scss +0 -0
  76. package/core/css/core/_alert.scss +87 -0
  77. package/core/css/core/_animations.scss +343 -0
  78. package/core/css/core/_bindings.scss +116 -0
  79. package/core/css/core/_blog-search.scss +57 -0
  80. package/core/css/core/_chatsy.scss +61 -0
  81. package/core/css/core/_consent.scss +424 -0
  82. package/core/css/core/_custom-font-awesome.scss +146 -0
  83. package/core/css/core/_exit-popup.scss +42 -0
  84. package/core/css/core/_generated-badge.scss +33 -0
  85. package/core/css/core/_initialize.scss +29 -0
  86. package/core/css/core/_lazy-loading.scss +104 -0
  87. package/core/css/core/_social-sharing.scss +41 -0
  88. package/core/css/core/_tones.scss +32 -0
  89. package/core/css/core/_utilities.scss +157 -0
  90. package/core/css/main.scss +54 -0
  91. package/core/css/motion/_index.scss +368 -0
  92. package/core/css/pages/404/index.scss +0 -0
  93. package/core/css/pages/admin/calendar/index.scss +692 -0
  94. package/core/css/pages/admin/firebase/index.scss +33 -0
  95. package/core/css/pages/admin/index.scss +9 -0
  96. package/core/css/pages/admin/users/index.scss +9 -0
  97. package/core/css/pages/admin/verts/index.scss +9 -0
  98. package/core/css/pages/alternatives/[alternative]/index.scss +0 -0
  99. package/core/css/pages/blog/[slug].scss +55 -0
  100. package/core/css/pages/dashboard/account/index.scss +242 -0
  101. package/core/css/pages/download/index.scss +480 -0
  102. package/core/css/pages/extension/installed/index.scss +317 -0
  103. package/core/css/pages/feedback/index.scss +31 -0
  104. package/core/css/pages/payment/confirmation/index.scss +14 -0
  105. package/core/css/pages/pricing/index.scss +0 -0
  106. package/core/css/pages/status/index.scss +162 -0
  107. package/core/css/pages/test/libraries/layers/index.scss +28 -0
  108. package/core/css/pages/updates/[update].scss +46 -0
  109. package/core/css/pages/updates/index.scss +13 -0
  110. package/core/css/shell/_index.scss +207 -0
  111. package/core/css/tokens/_index.scss +166 -0
  112. package/core/icons/flags/ar.svg +1 -0
  113. package/core/icons/flags/bd.svg +1 -0
  114. package/core/icons/flags/ca.svg +1 -0
  115. package/core/icons/flags/ch.svg +1 -0
  116. package/core/icons/flags/cn.svg +1 -0
  117. package/core/icons/flags/co.svg +1 -0
  118. package/core/icons/flags/cw.svg +1 -0
  119. package/core/icons/flags/cz.svg +1 -0
  120. package/core/icons/flags/de.svg +1 -0
  121. package/core/icons/flags/dk.svg +1 -0
  122. package/core/icons/flags/eg.svg +1 -0
  123. package/core/icons/flags/es.svg +1 -0
  124. package/core/icons/flags/fi.svg +1 -0
  125. package/core/icons/flags/fr.svg +1 -0
  126. package/core/icons/flags/gb.svg +1 -0
  127. package/core/icons/flags/gq.svg +1 -0
  128. package/core/icons/flags/gr.svg +1 -0
  129. package/core/icons/flags/gt.svg +1 -0
  130. package/core/icons/flags/hn.svg +1 -0
  131. package/core/icons/flags/id.svg +1 -0
  132. package/core/icons/flags/ie.svg +1 -0
  133. package/core/icons/flags/in.svg +1 -0
  134. package/core/icons/flags/ir.svg +1 -0
  135. package/core/icons/flags/it.svg +1 -0
  136. package/core/icons/flags/jp.svg +1 -0
  137. package/core/icons/flags/kp.svg +1 -0
  138. package/core/icons/flags/kr.svg +1 -0
  139. package/core/icons/flags/ly.svg +1 -0
  140. package/core/icons/flags/ma.svg +1 -0
  141. package/core/icons/flags/mu.svg +1 -0
  142. package/core/icons/flags/mx.svg +1 -0
  143. package/core/icons/flags/ng.svg +1 -0
  144. package/core/icons/flags/pa.svg +1 -0
  145. package/core/icons/flags/ph.svg +1 -0
  146. package/core/icons/flags/pk.svg +1 -0
  147. package/core/icons/flags/pt.svg +1 -0
  148. package/core/icons/flags/ru.svg +1 -0
  149. package/core/icons/flags/sa.svg +1 -0
  150. package/core/icons/flags/sd.svg +1 -0
  151. package/core/icons/flags/se.svg +1 -0
  152. package/core/icons/flags/th.svg +1 -0
  153. package/core/icons/flags/tm.svg +1 -0
  154. package/core/icons/flags/tr.svg +1 -0
  155. package/core/icons/flags/us.svg +1 -0
  156. package/core/icons/flags/uy.svg +1 -0
  157. package/core/icons/flags/vn.svg +1 -0
  158. package/core/icons/flags/za.svg +1 -0
  159. package/core/images/exit-popup/subscriber-1.jpg +0 -0
  160. package/core/images/exit-popup/subscriber-2.jpg +0 -0
  161. package/core/images/exit-popup/subscriber-3.jpg +0 -0
  162. package/core/images/exit-popup/subscriber-4.jpg +0 -0
  163. package/core/images/placeholder/photo-1.jpg +0 -0
  164. package/core/images/placeholder/photo-2.jpg +0 -0
  165. package/core/images/placeholder/photo-3.jpg +0 -0
  166. package/core/js/core/analytics-loader.js +253 -0
  167. package/core/js/core/app-shell.js +136 -0
  168. package/core/js/core/appearance.js +195 -0
  169. package/core/js/core/auth.js +457 -0
  170. package/core/js/core/complete.js +72 -0
  171. package/core/js/core/consent.js +384 -0
  172. package/core/js/core/dev-icon-audit.js +63 -0
  173. package/core/js/core/dev-palette.js +670 -0
  174. package/core/js/core/dev-sections.js +53 -0
  175. package/core/js/core/exit-popup.js +276 -0
  176. package/core/js/core/initialize.js +8 -0
  177. package/core/js/core/language-switcher.js +167 -0
  178. package/core/js/core/lazy-loading.js +479 -0
  179. package/core/js/core/motion.js +28 -0
  180. package/core/js/core/query-strings.js +146 -0
  181. package/core/js/core/service-worker.js +28 -0
  182. package/core/js/core/social-sharing.js +293 -0
  183. package/core/js/first-paint.js +35 -0
  184. package/core/js/layouts/modules/utilities/redirect.js +122 -0
  185. package/core/js/libs/admin-helpers.js +62 -0
  186. package/core/js/libs/alert-dismiss.js +35 -0
  187. package/core/js/libs/analytics.js +368 -0
  188. package/core/js/libs/auth/email.js +144 -0
  189. package/core/js/libs/auth/errors.js +119 -0
  190. package/core/js/libs/auth/forms.js +206 -0
  191. package/core/js/libs/auth/index.js +181 -0
  192. package/core/js/libs/auth/oauth.js +394 -0
  193. package/core/js/libs/auth/orphan.js +110 -0
  194. package/core/js/libs/auth/password-toggle.js +44 -0
  195. package/core/js/libs/auth/session-params.js +266 -0
  196. package/core/js/libs/auth/tracking.js +41 -0
  197. package/core/js/libs/charts.js +516 -0
  198. package/core/js/libs/consent-region.js +127 -0
  199. package/core/js/libs/dev.js +150 -0
  200. package/core/js/libs/graph.js +201 -0
  201. package/core/js/libs/initialize-tooltips.js +31 -0
  202. package/core/js/libs/logger.js +38 -0
  203. package/core/js/libs/omega-copy.js +89 -0
  204. package/core/js/libs/path-prefix.js +44 -0
  205. package/core/js/libs/payment-config.js +76 -0
  206. package/core/js/libs/recaptcha.js +94 -0
  207. package/core/js/libs/sale-name.js +295 -0
  208. package/core/js/libs/showcase-frames.js +37 -0
  209. package/core/js/libs/srcset.js +69 -0
  210. package/core/js/libs/tracking-consent.js +161 -0
  211. package/core/js/main.js +144 -0
  212. package/core/js/pages/404/index.js +57 -0
  213. package/core/js/pages/admin/calendar/calendar-core.js +701 -0
  214. package/core/js/pages/admin/calendar/calendar-events.js +853 -0
  215. package/core/js/pages/admin/calendar/calendar-renderer.js +807 -0
  216. package/core/js/pages/admin/calendar/campaign-preview.js +105 -0
  217. package/core/js/pages/admin/calendar/index.js +40 -0
  218. package/core/js/pages/admin/firebase/index.js +727 -0
  219. package/core/js/pages/admin/index.js +531 -0
  220. package/core/js/pages/admin/posts/editor/index.js +230 -0
  221. package/core/js/pages/admin/posts/index.js +237 -0
  222. package/core/js/pages/admin/studio/index.js +538 -0
  223. package/core/js/pages/admin/users/index.js +669 -0
  224. package/core/js/pages/admin/users/new/index.js +65 -0
  225. package/core/js/pages/admin/verts/index.js +407 -0
  226. package/core/js/pages/alternatives/[alternative]/index.js +17 -0
  227. package/core/js/pages/app/index.js +135 -0
  228. package/core/js/pages/blog/[slug].js +132 -0
  229. package/core/js/pages/blog/_search.mjs +108 -0
  230. package/core/js/pages/blog/index.js +193 -0
  231. package/core/js/pages/blog/page/[num]/index.js +7 -0
  232. package/core/js/pages/connections/callback/index.js +148 -0
  233. package/core/js/pages/contact/index.js +170 -0
  234. package/core/js/pages/cookies.js +2 -0
  235. package/core/js/pages/dashboard/account/index.js +366 -0
  236. package/core/js/pages/dashboard/account/modules/orders.js +82 -0
  237. package/core/js/pages/dashboard/account/sections/api-keys.js +149 -0
  238. package/core/js/pages/dashboard/account/sections/billing.js +1667 -0
  239. package/core/js/pages/dashboard/account/sections/connections.js +333 -0
  240. package/core/js/pages/dashboard/account/sections/data-request.js +225 -0
  241. package/core/js/pages/dashboard/account/sections/delete.js +80 -0
  242. package/core/js/pages/dashboard/account/sections/notifications.js +167 -0
  243. package/core/js/pages/dashboard/account/sections/orders.js +151 -0
  244. package/core/js/pages/dashboard/account/sections/profile.js +200 -0
  245. package/core/js/pages/dashboard/account/sections/referrals.js +217 -0
  246. package/core/js/pages/dashboard/account/sections/refund.js +253 -0
  247. package/core/js/pages/dashboard/account/sections/security.js +726 -0
  248. package/core/js/pages/dashboard/account/sections/team.js +247 -0
  249. package/core/js/pages/download/index.js +418 -0
  250. package/core/js/pages/extension/index.js +164 -0
  251. package/core/js/pages/extension/installed/index.js +135 -0
  252. package/core/js/pages/feedback/index.js +193 -0
  253. package/core/js/pages/legal/_document.js +91 -0
  254. package/core/js/pages/payment/checkout/index.js +420 -0
  255. package/core/js/pages/payment/checkout/modules/api.js +139 -0
  256. package/core/js/pages/payment/checkout/modules/dev-section.js +175 -0
  257. package/core/js/pages/payment/checkout/modules/discount.js +69 -0
  258. package/core/js/pages/payment/checkout/modules/pricing.js +73 -0
  259. package/core/js/pages/payment/checkout/modules/state.js +339 -0
  260. package/core/js/pages/payment/checkout/modules/tracking.js +54 -0
  261. package/core/js/pages/payment/confirmation/index.js +91 -0
  262. package/core/js/pages/payment/confirmation/modules/celebration.js +45 -0
  263. package/core/js/pages/payment/confirmation/modules/state.js +112 -0
  264. package/core/js/pages/payment/confirmation/modules/tracking.js +133 -0
  265. package/core/js/pages/payment/confirmation/modules/verify.js +142 -0
  266. package/core/js/pages/portal/email-preferences/index.js +183 -0
  267. package/core/js/pages/pricing/index.js +425 -0
  268. package/core/js/pages/privacy.js +2 -0
  269. package/core/js/pages/reset/index.js +12 -0
  270. package/core/js/pages/signin/index.js +22 -0
  271. package/core/js/pages/signup/index.js +23 -0
  272. package/core/js/pages/status/index.js +687 -0
  273. package/core/js/pages/terms.js +2 -0
  274. package/core/js/pages/test/components/[category]/[name]/index.js +26 -0
  275. package/core/js/pages/test/libraries/appearance/index.js +220 -0
  276. package/core/js/pages/test/libraries/form-manager/index.js +328 -0
  277. package/core/js/pages/test/libraries/layers/index.js +16 -0
  278. package/core/js/pages/test/libraries/verts/index.js +68 -0
  279. package/core/js/pages/test/sections/[category]/[name]/index.js +28 -0
  280. package/core/js/pages/test/styleguide/index.js +35 -0
  281. package/core/js/pages/token/index.js +203 -0
  282. package/core/logos/brandmarks/original/apple-app-store.svg +1 -0
  283. package/core/logos/brandmarks/original/apple.svg +6 -0
  284. package/core/logos/brandmarks/original/bitbucket.svg +14 -0
  285. package/core/logos/brandmarks/original/bitcoin.svg +1 -0
  286. package/core/logos/brandmarks/original/bluesky.svg +7 -0
  287. package/core/logos/brandmarks/original/bootstrap.svg +43 -0
  288. package/core/logos/brandmarks/original/brave.svg +1 -0
  289. package/core/logos/brandmarks/original/chrome.svg +1 -0
  290. package/core/logos/brandmarks/original/discord.svg +1 -0
  291. package/core/logos/brandmarks/original/dropbox.svg +10 -0
  292. package/core/logos/brandmarks/original/edge.svg +1 -0
  293. package/core/logos/brandmarks/original/facebook-f.svg +1 -0
  294. package/core/logos/brandmarks/original/facebook.svg +8 -0
  295. package/core/logos/brandmarks/original/figma.svg +10 -0
  296. package/core/logos/brandmarks/original/firefox.svg +1 -0
  297. package/core/logos/brandmarks/original/github.svg +6 -0
  298. package/core/logos/brandmarks/original/gitlab.svg +17 -0
  299. package/core/logos/brandmarks/original/google-a-s.svg +1 -0
  300. package/core/logos/brandmarks/original/google-chrome-webstore.svg +49 -0
  301. package/core/logos/brandmarks/original/google-play-store.svg +1 -0
  302. package/core/logos/brandmarks/original/google-web-search.svg +1 -0
  303. package/core/logos/brandmarks/original/google.svg +1 -0
  304. package/core/logos/brandmarks/original/gulp.svg +4 -0
  305. package/core/logos/brandmarks/original/html-5.svg +1 -0
  306. package/core/logos/brandmarks/original/instagram.svg +1 -0
  307. package/core/logos/brandmarks/original/javascript.svg +1 -0
  308. package/core/logos/brandmarks/original/jira.svg +18 -0
  309. package/core/logos/brandmarks/original/kick.svg +7 -0
  310. package/core/logos/brandmarks/original/linkedin.svg +1 -0
  311. package/core/logos/brandmarks/original/linux.svg +28 -0
  312. package/core/logos/brandmarks/original/messenger.svg +16 -0
  313. package/core/logos/brandmarks/original/microsoft.svg +1 -0
  314. package/core/logos/brandmarks/original/npm.svg +8 -0
  315. package/core/logos/brandmarks/original/opera.svg +1 -0
  316. package/core/logos/brandmarks/original/patreon.svg +6 -0
  317. package/core/logos/brandmarks/original/pinterest.svg +6 -0
  318. package/core/logos/brandmarks/original/reddit.svg +1 -0
  319. package/core/logos/brandmarks/original/safari.svg +31 -0
  320. package/core/logos/brandmarks/original/sass.svg +3 -0
  321. package/core/logos/brandmarks/original/signal.svg +6 -0
  322. package/core/logos/brandmarks/original/skype.svg +47 -0
  323. package/core/logos/brandmarks/original/slack.svg +33 -0
  324. package/core/logos/brandmarks/original/soundcloud.svg +1 -0
  325. package/core/logos/brandmarks/original/spotify.svg +5 -0
  326. package/core/logos/brandmarks/original/steam.svg +6 -0
  327. package/core/logos/brandmarks/original/telegram.svg +14 -0
  328. package/core/logos/brandmarks/original/tiktok.svg +1 -0
  329. package/core/logos/brandmarks/original/trello.svg +16 -0
  330. package/core/logos/brandmarks/original/tumblr.svg +1 -0
  331. package/core/logos/brandmarks/original/twitch.svg +6 -0
  332. package/core/logos/brandmarks/original/twitter.svg +6 -0
  333. package/core/logos/brandmarks/original/unity.svg +6 -0
  334. package/core/logos/brandmarks/original/x.svg +7 -0
  335. package/core/logos/brandmarks/original/yahoo-y.svg +1 -0
  336. package/core/logos/brandmarks/original/youtube-square.svg +1 -0
  337. package/core/logos/brandmarks/original/youtube.svg +1 -0
  338. package/core/logos/combomarks/dark/airbnb.svg +30 -0
  339. package/core/logos/combomarks/dark/slack.svg +38 -0
  340. package/core/logos/combomarks/dark/spotify.svg +35 -0
  341. package/core/logos/combomarks/dark/twilio.svg +26 -0
  342. package/core/logos/combomarks/dark/weebly.svg +35 -0
  343. package/core/logos/combomarks/gray/airbnb.svg +33 -0
  344. package/core/logos/combomarks/gray/alphabet.svg +23 -0
  345. package/core/logos/combomarks/gray/amazon.svg +38 -0
  346. package/core/logos/combomarks/gray/boring-company.svg +91 -0
  347. package/core/logos/combomarks/gray/burberry.svg +239 -0
  348. package/core/logos/combomarks/gray/embark.svg +21 -0
  349. package/core/logos/combomarks/gray/fitbit.svg +61 -0
  350. package/core/logos/combomarks/gray/fred-perry.svg +94 -0
  351. package/core/logos/combomarks/gray/google.svg +27 -0
  352. package/core/logos/combomarks/gray/hollister.svg +34 -0
  353. package/core/logos/combomarks/gray/hubspot.svg +27 -0
  354. package/core/logos/combomarks/gray/kaplan.svg +33 -0
  355. package/core/logos/combomarks/gray/lenovo.svg +27 -0
  356. package/core/logos/combomarks/gray/levis.svg +25 -0
  357. package/core/logos/combomarks/gray/mapbox.svg +37 -0
  358. package/core/logos/combomarks/gray/netflix.svg +15 -0
  359. package/core/logos/combomarks/gray/new-balance.svg +32 -0
  360. package/core/logos/combomarks/gray/nike.svg +17 -0
  361. package/core/logos/combomarks/gray/paypal.svg +41 -0
  362. package/core/logos/combomarks/gray/puma.svg +82 -0
  363. package/core/logos/combomarks/gray/samsung.svg +26 -0
  364. package/core/logos/combomarks/gray/shopify.svg +36 -0
  365. package/core/logos/combomarks/gray/slack.svg +42 -0
  366. package/core/logos/combomarks/gray/spotify.svg +38 -0
  367. package/core/logos/combomarks/gray/stripe.svg +24 -0
  368. package/core/logos/combomarks/gray/tnf.svg +30 -0
  369. package/core/logos/combomarks/gray/twilio.svg +29 -0
  370. package/core/logos/combomarks/gray/uber.svg +24 -0
  371. package/core/logos/combomarks/gray/weebly.svg +38 -0
  372. package/core/logos/combomarks/original/airbnb.svg +33 -0
  373. package/core/logos/combomarks/original/alphabet.svg +23 -0
  374. package/core/logos/combomarks/original/amazon.svg +38 -0
  375. package/core/logos/combomarks/original/asana.svg +8 -0
  376. package/core/logos/combomarks/original/boring-company.svg +91 -0
  377. package/core/logos/combomarks/original/discord.svg +6 -0
  378. package/core/logos/combomarks/original/embark.svg +24 -0
  379. package/core/logos/combomarks/original/fitbit.svg +60 -0
  380. package/core/logos/combomarks/original/github.svg +6 -0
  381. package/core/logos/combomarks/original/gitlab.svg +20 -0
  382. package/core/logos/combomarks/original/google.svg +28 -0
  383. package/core/logos/combomarks/original/hubspot.svg +29 -0
  384. package/core/logos/combomarks/original/instagram.svg +7 -0
  385. package/core/logos/combomarks/original/kickstarter.svg +6 -0
  386. package/core/logos/combomarks/original/lenovo.svg +24 -0
  387. package/core/logos/combomarks/original/linkedin.svg +6 -0
  388. package/core/logos/combomarks/original/mapbox.svg +39 -0
  389. package/core/logos/combomarks/original/mastodon.svg +7 -0
  390. package/core/logos/combomarks/original/medium.svg +6 -0
  391. package/core/logos/combomarks/original/meta.svg +22 -0
  392. package/core/logos/combomarks/original/microsoft.svg +13 -0
  393. package/core/logos/combomarks/original/monday.svg +12 -0
  394. package/core/logos/combomarks/original/netflix.svg +15 -0
  395. package/core/logos/combomarks/original/notion.svg +9 -0
  396. package/core/logos/combomarks/original/paypal.svg +41 -0
  397. package/core/logos/combomarks/original/reddit.svg +13 -0
  398. package/core/logos/combomarks/original/samsung.svg +25 -0
  399. package/core/logos/combomarks/original/shopify.svg +35 -0
  400. package/core/logos/combomarks/original/slack.svg +47 -0
  401. package/core/logos/combomarks/original/spotify.svg +37 -0
  402. package/core/logos/combomarks/original/stackoverflow.svg +28 -0
  403. package/core/logos/combomarks/original/stripe.svg +24 -0
  404. package/core/logos/combomarks/original/tiktok.svg +12 -0
  405. package/core/logos/combomarks/original/tumblr.svg +6 -0
  406. package/core/logos/combomarks/original/uber.svg +24 -0
  407. package/core/logos/combomarks/original/weebly.svg +34 -0
  408. package/core/logos/combomarks/original/whatsapp.svg +20 -0
  409. package/core/logos/combomarks/original/youtube.svg +10 -0
  410. package/core/logos/combomarks/original/zoom.svg +7 -0
  411. package/core/logos/combomarks/white/airbnb.svg +33 -0
  412. package/core/logos/combomarks/white/alphabet.svg +23 -0
  413. package/core/logos/combomarks/white/amazon.svg +38 -0
  414. package/core/logos/combomarks/white/boring-company.svg +91 -0
  415. package/core/logos/combomarks/white/embark.svg +21 -0
  416. package/core/logos/combomarks/white/fitbit.svg +61 -0
  417. package/core/logos/combomarks/white/google.svg +27 -0
  418. package/core/logos/combomarks/white/hubspot.svg +27 -0
  419. package/core/logos/combomarks/white/kaplan.svg +33 -0
  420. package/core/logos/combomarks/white/lenovo.svg +27 -0
  421. package/core/logos/combomarks/white/mapbox.svg +37 -0
  422. package/core/logos/combomarks/white/netflix.svg +15 -0
  423. package/core/logos/combomarks/white/paypal.svg +40 -0
  424. package/core/logos/combomarks/white/samsung.svg +26 -0
  425. package/core/logos/combomarks/white/shopify.svg +36 -0
  426. package/core/logos/combomarks/white/slack.svg +42 -0
  427. package/core/logos/combomarks/white/spotify.svg +38 -0
  428. package/core/logos/combomarks/white/stripe.svg +24 -0
  429. package/core/logos/combomarks/white/twilio.svg +29 -0
  430. package/core/logos/combomarks/white/uber.svg +24 -0
  431. package/core/logos/combomarks/white/weebly.svg +38 -0
  432. package/defaults/pages/404.md +4 -0
  433. package/defaults/pages/about.md +4 -0
  434. package/defaults/pages/admin/calendar/index.html +4 -0
  435. package/defaults/pages/admin/firebase/index.html +4 -0
  436. package/defaults/pages/admin/index.md +4 -0
  437. package/defaults/pages/admin/posts/editor.html +4 -0
  438. package/defaults/pages/admin/posts/index.html +4 -0
  439. package/defaults/pages/admin/stackblitz/index.html +4 -0
  440. package/defaults/pages/admin/studio/index.html +4 -0
  441. package/defaults/pages/admin/users/index.html +4 -0
  442. package/defaults/pages/admin/users/new.html +4 -0
  443. package/defaults/pages/admin/verts/index.html +4 -0
  444. package/defaults/pages/ads-txt.html +11 -0
  445. package/defaults/pages/alternatives/index.md +4 -0
  446. package/defaults/pages/app.md +4 -0
  447. package/defaults/pages/auth/reset.md +4 -0
  448. package/defaults/pages/auth/signin.md +4 -0
  449. package/defaults/pages/auth/signup.md +4 -0
  450. package/defaults/pages/auth/token.md +4 -0
  451. package/defaults/pages/blog/categories/category.html +21 -0
  452. package/defaults/pages/blog/categories.md +4 -0
  453. package/defaults/pages/blog/index-json.html +27 -0
  454. package/defaults/pages/blog/tags/tag.html +21 -0
  455. package/defaults/pages/blog/tags.md +4 -0
  456. package/defaults/pages/blog.md +22 -0
  457. package/defaults/pages/careers.md +4 -0
  458. package/defaults/pages/connections/callback.md +4 -0
  459. package/defaults/pages/contact.md +4 -0
  460. package/defaults/pages/dashboard/account.md +4 -0
  461. package/defaults/pages/dashboard/index.md +12 -0
  462. package/defaults/pages/download.md +4 -0
  463. package/defaults/pages/extension/index.html +4 -0
  464. package/defaults/pages/extension/installed.html +4 -0
  465. package/defaults/pages/feedback.md +4 -0
  466. package/defaults/pages/feeds/posts-json.html +40 -0
  467. package/defaults/pages/feeds/posts-xml.html +38 -0
  468. package/defaults/pages/humans.html +25 -0
  469. package/defaults/pages/index.md +4 -0
  470. package/defaults/pages/legal/cookies.md +4 -0
  471. package/defaults/pages/legal/privacy.md +4 -0
  472. package/defaults/pages/legal/terms.md +4 -0
  473. package/defaults/pages/llms-txt.html +46 -0
  474. package/defaults/pages/opensearch.html +16 -0
  475. package/defaults/pages/pages-json.html +42 -0
  476. package/defaults/pages/payment/checkout.md +4 -0
  477. package/defaults/pages/payment/confirmation.md +4 -0
  478. package/defaults/pages/portal/email-preferences.md +4 -0
  479. package/defaults/pages/pricing.md +4 -0
  480. package/defaults/pages/redirects/account.md +12 -0
  481. package/defaults/pages/redirects/admin/dashboard.md +11 -0
  482. package/defaults/pages/redirects/auth/change-password.md +9 -0
  483. package/defaults/pages/redirects/auth/forgot.md +9 -0
  484. package/defaults/pages/redirects/auth/join.md +9 -0
  485. package/defaults/pages/redirects/auth/login.md +9 -0
  486. package/defaults/pages/redirects/auth/recover.md +9 -0
  487. package/defaults/pages/redirects/auth/register.md +9 -0
  488. package/defaults/pages/redirects/auth/reset-password.md +9 -0
  489. package/defaults/pages/redirects/billing/cancel.md +9 -0
  490. package/defaults/pages/redirects/billing/refund.md +9 -0
  491. package/defaults/pages/robots.html +36 -0
  492. package/defaults/pages/security-txt.html +11 -0
  493. package/defaults/pages/sitemap.html +37 -0
  494. package/defaults/pages/status.md +4 -0
  495. package/defaults/pages/team/index.md +4 -0
  496. package/defaults/pages/test/account/dashboard.html +16 -0
  497. package/defaults/pages/test/index.md +12 -0
  498. package/defaults/pages/test/libraries/appearance.html +268 -0
  499. package/defaults/pages/test/libraries/cover.html +50 -0
  500. package/defaults/pages/test/libraries/error.html +30 -0
  501. package/defaults/pages/test/libraries/firestore.html +202 -0
  502. package/defaults/pages/test/libraries/form-manager.html +374 -0
  503. package/defaults/pages/test/libraries/layers.html +52 -0
  504. package/defaults/pages/test/libraries/lazy-loading.html +450 -0
  505. package/defaults/pages/test/libraries/verts.html +149 -0
  506. package/defaults/pages/test/redirect/external.md +13 -0
  507. package/defaults/pages/test/redirect/internal.md +13 -0
  508. package/defaults/pages/test/styleguide/index.html +588 -0
  509. package/defaults/pages/test/translation/index.md +50 -0
  510. package/defaults/pages/updates/index.md +4 -0
  511. package/defaults/sample-posts/2026-01-12-welcome-to-the-blog.md +26 -0
  512. package/defaults/sample-posts/2026-02-03-a-field-guide-to-good-defaults.md +50 -0
  513. package/defaults/sample-posts/2026-02-24-shipping-week-notes.md +37 -0
  514. package/defaults/sample-posts/2026-03-17-the-case-for-one-config.md +36 -0
  515. package/defaults/sample-posts/2026-04-09-write-release-notes-people-read.md +34 -0
  516. package/defaults/sample-posts/2026-05-21-dark-mode-done-properly.md +38 -0
  517. package/defaults/sample-posts/2026-06-02-the-boring-deploy-is-the-good-deploy.md +30 -0
  518. package/defaults/sample-posts/2026-06-11-naming-things-a-survival-guide.md +30 -0
  519. package/defaults/sample-posts/2026-06-19-your-pricing-page-is-a-product.md +28 -0
  520. package/defaults/sample-posts/2026-06-30-email-is-an-api-with-feelings.md +28 -0
  521. package/defaults/sample-posts/2026-07-08-ship-the-docs-with-the-diff.md +30 -0
  522. package/defaults/sample-team/avery-quinn.md +22 -0
  523. package/defaults/sample-team/jordan-reyes.md +25 -0
  524. package/defaults/sample-team/riley-nakamura.md +22 -0
  525. package/defaults/sample-team/sam-okafor.md +22 -0
  526. package/defaults/sample-updates/v1.0.0.md +28 -0
  527. package/defaults/sample-updates/v1.1.0.md +27 -0
  528. package/defaults/sample-updates/v1.2.0.md +27 -0
  529. package/defaults/sample-updates/v1.3.0.md +28 -0
  530. package/defaults/showcase/components.html +62 -0
  531. package/defaults/showcase/entry.html +76 -0
  532. package/defaults/showcase/frame.html +62 -0
  533. package/defaults/showcase/index.html +59 -0
  534. package/dist/assets.js +1349 -0
  535. package/dist/brand-tokens.js +195 -0
  536. package/dist/build.js +192 -0
  537. package/dist/cachebreak-html.js +71 -0
  538. package/dist/cli-run.js +25 -0
  539. package/dist/cli.js +47 -0
  540. package/dist/collections.js +183 -0
  541. package/dist/commands/audit.js +470 -0
  542. package/dist/commands/build.js +197 -0
  543. package/dist/commands/clean.js +18 -0
  544. package/dist/commands/customize.js +125 -0
  545. package/dist/commands/deploy.js +441 -0
  546. package/dist/commands/dev.js +1067 -0
  547. package/dist/commands/install.js +31 -0
  548. package/dist/commands/lib/deploy-precheck.js +43 -0
  549. package/dist/commands/lib/ensure-target.js +115 -0
  550. package/dist/commands/migrate.js +86 -0
  551. package/dist/commands/purge.js +31 -0
  552. package/dist/commands/test.js +237 -0
  553. package/dist/commands/translate.js +57 -0
  554. package/dist/commands/update.js +22 -0
  555. package/dist/commands/version.js +11 -0
  556. package/dist/config-sections.js +245 -0
  557. package/dist/consumer-scan.js +118 -0
  558. package/dist/consumer.js +49 -0
  559. package/dist/customize.js +328 -0
  560. package/dist/decisions.js +153 -0
  561. package/dist/dist-audit.js +417 -0
  562. package/dist/dynamic-pages.js +357 -0
  563. package/dist/engine.js +1402 -0
  564. package/dist/firebase-auth-helpers.js +198 -0
  565. package/dist/font-metrics.js +420 -0
  566. package/dist/frontmatter-liquid.js +253 -0
  567. package/dist/github-secrets.js +80 -0
  568. package/dist/hero-animations.js +170 -0
  569. package/dist/imagemin.js +261 -0
  570. package/dist/index.js +41 -0
  571. package/dist/inline-icons.js +65 -0
  572. package/dist/language-flags.js +55 -0
  573. package/dist/layers.js +80 -0
  574. package/dist/layouts.js +44 -0
  575. package/dist/limit-collections.js +199 -0
  576. package/dist/link-resolver.js +303 -0
  577. package/dist/markdown-images.js +85 -0
  578. package/dist/merge.js +20 -0
  579. package/dist/migrate/codemod.js +172 -0
  580. package/dist/migrate/config-convert.js +448 -0
  581. package/dist/migrate/consumer-assets.js +130 -0
  582. package/dist/migrate/index.js +223 -0
  583. package/dist/migrate/lint.js +108 -0
  584. package/dist/migrate/rules.js +1929 -0
  585. package/dist/minify-html.js +148 -0
  586. package/dist/mode-helpers.js +96 -0
  587. package/dist/omega-bin.js +7 -0
  588. package/dist/overrides.js +190 -0
  589. package/dist/path-prefix.js +167 -0
  590. package/dist/paths.js +37 -0
  591. package/dist/pricing.js +301 -0
  592. package/dist/purge.js +95 -0
  593. package/dist/redirect-page.js +45 -0
  594. package/dist/sample-content.js +210 -0
  595. package/dist/scaffold.js +123 -0
  596. package/dist/sections.js +810 -0
  597. package/dist/service-worker.js +200 -0
  598. package/dist/social-pages.js +142 -0
  599. package/dist/srcset.js +67 -0
  600. package/dist/static-assets.js +154 -0
  601. package/dist/target-shortlinks.js +123 -0
  602. package/dist/theme-vocabulary.js +76 -0
  603. package/dist/translate/collect-text-nodes.js +93 -0
  604. package/dist/translate/default-routes.js +152 -0
  605. package/dist/translate/generate-defaults.js +297 -0
  606. package/dist/translate/index.js +568 -0
  607. package/dist/translate/packaged-defaults.js +127 -0
  608. package/dist/translate/sitemap.js +108 -0
  609. package/dist/vendor/account/engine.js +182 -0
  610. package/dist/vendor/account/features.js +220 -0
  611. package/dist/vendor/account/index.js +53 -0
  612. package/dist/vendor/account/schema.js +272 -0
  613. package/dist/vendor/account/subscription.js +38 -0
  614. package/dist/vendor/config/company.js +31 -0
  615. package/dist/vendor/config/defaults.js +173 -0
  616. package/dist/vendor/config/demo.js +18 -0
  617. package/dist/vendor/config/desktop-artifacts.js +110 -0
  618. package/dist/vendor/config/edit.js +769 -0
  619. package/dist/vendor/config/env-delivery.js +145 -0
  620. package/dist/vendor/config/env-rules.js +93 -0
  621. package/dist/vendor/config/env-schema.js +1078 -0
  622. package/dist/vendor/config/env.js +445 -0
  623. package/dist/vendor/config/hooks.js +97 -0
  624. package/dist/vendor/config/index.js +237 -0
  625. package/dist/vendor/config/instances.js +208 -0
  626. package/dist/vendor/config/load.js +490 -0
  627. package/dist/vendor/config/merge.js +68 -0
  628. package/dist/vendor/config/order.js +139 -0
  629. package/dist/vendor/config/ports.js +374 -0
  630. package/dist/vendor/config/providers.js +32 -0
  631. package/dist/vendor/config/repo.js +142 -0
  632. package/dist/vendor/config/retired-keys.js +430 -0
  633. package/dist/vendor/config/schema.js +1610 -0
  634. package/dist/vendor/config/secrets.js +50 -0
  635. package/dist/vendor/config/seed.js +34 -0
  636. package/dist/vendor/config/site-global.js +205 -0
  637. package/dist/vendor/config/validate.js +554 -0
  638. package/dist/vendor/config/winback.js +61 -0
  639. package/dist/vendor/devkit/actions-secrets.js +145 -0
  640. package/dist/vendor/devkit/attach-log-file.js +262 -0
  641. package/dist/vendor/devkit/bare-requires.js +153 -0
  642. package/dist/vendor/devkit/bundle.js +292 -0
  643. package/dist/vendor/devkit/ci-workflows.js +520 -0
  644. package/dist/vendor/devkit/cli-router.js +156 -0
  645. package/dist/vendor/devkit/command-path.js +46 -0
  646. package/dist/vendor/devkit/defaults-engine.js +372 -0
  647. package/dist/vendor/devkit/deploy-precheck.js +54 -0
  648. package/dist/vendor/devkit/deploy-record.js +180 -0
  649. package/dist/vendor/devkit/deploy.js +286 -0
  650. package/dist/vendor/devkit/env-watch.js +132 -0
  651. package/dist/vendor/devkit/framework-deps.js +46 -0
  652. package/dist/vendor/devkit/icons.js +178 -0
  653. package/dist/vendor/devkit/license.js +155 -0
  654. package/dist/vendor/devkit/local-https.js +360 -0
  655. package/dist/vendor/devkit/local.js +1905 -0
  656. package/dist/vendor/devkit/logger.js +128 -0
  657. package/dist/vendor/devkit/merge-line-files.js +296 -0
  658. package/dist/vendor/devkit/omega-bin.js +345 -0
  659. package/dist/vendor/devkit/reads.js +274 -0
  660. package/dist/vendor/devkit/safe-install.js +18 -0
  661. package/dist/vendor/devkit/scaffold-guard.js +96 -0
  662. package/dist/vendor/devkit/stop-signals.js +28 -0
  663. package/dist/vendor/devkit/strip-dev-blocks-plugin.js +29 -0
  664. package/dist/vendor/devkit/strip-dev-blocks.js +32 -0
  665. package/dist/vendor/devkit/target-secrets.js +165 -0
  666. package/dist/vendor/devkit/test/scope.js +162 -0
  667. package/dist/vendor/devkit/translate/cache.js +84 -0
  668. package/dist/vendor/devkit/translate/engine.js +243 -0
  669. package/dist/vendor/devkit/translate/index.js +49 -0
  670. package/dist/vendor/devkit/translate/languages.js +134 -0
  671. package/dist/vendor/devkit/translate/providers.js +188 -0
  672. package/dist/vendor/devkit/update.js +569 -0
  673. package/dist/vendor/template-kit/data/language-flags.js +54 -0
  674. package/dist/vendor/template-kit/data/languages.js +195 -0
  675. package/dist/vendor/template-kit/data/social-urls.js +26 -0
  676. package/dist/vendor/template-kit/filters.js +227 -0
  677. package/dist/vendor/template-kit/index.js +28 -0
  678. package/dist/vendor/template-kit/jekyll-compat.js +268 -0
  679. package/dist/vendor/template-kit/register-liquid.js +0 -0
  680. package/dist/vendor/template-kit/tags/collections.js +191 -0
  681. package/dist/vendor/template-kit/tags/conditionals.js +101 -0
  682. package/dist/vendor/template-kit/tags/content.js +175 -0
  683. package/dist/vendor/template-kit/tags/index.js +33 -0
  684. package/dist/vendor/template-kit/tags/media.js +376 -0
  685. package/dist/vendor/template-kit/variable-resolver.js +163 -0
  686. package/docs/ads-system.md +109 -0
  687. package/docs/classy-v2/DIRECTION.md +92 -0
  688. package/docs/classy-v2/draft-1.html +1932 -0
  689. package/docs/classy-v2/draft-2.html +1956 -0
  690. package/docs/frontmatter.md +153 -0
  691. package/docs/index.md +254 -0
  692. package/docs/libs.md +298 -0
  693. package/docs/omega-sections-spec.md +159 -0
  694. package/docs/page-contract.md +112 -0
  695. package/docs/sections.md +845 -0
  696. package/docs/shared/agent-docs.md +89 -0
  697. package/docs/shared/analytics.md +612 -0
  698. package/docs/shared/brands.md +51 -0
  699. package/docs/shared/breaking-changes.md +497 -0
  700. package/docs/shared/config.md +1387 -0
  701. package/docs/shared/deploys.md +215 -0
  702. package/docs/shared/icons.md +201 -0
  703. package/docs/shared/local-dev.md +147 -0
  704. package/docs/shared/logging.md +202 -0
  705. package/docs/shared/monitoring.md +153 -0
  706. package/docs/shared/publishing.md +183 -0
  707. package/docs/shared/rulings.md +34 -0
  708. package/docs/shared/testing.md +147 -0
  709. package/docs/shared/theming.md +604 -0
  710. package/docs/shared/translation.md +291 -0
  711. package/docs/shared/updates.md +61 -0
  712. package/docs/template-kit.md +128 -0
  713. package/docs/test-framework.md +163 -0
  714. package/package.json +122 -0
  715. package/runtime/boot.js +149 -0
  716. package/runtime/icons.js +77 -0
  717. package/runtime/manager.js +36 -0
  718. package/scaffold/.github/workflows/build.yml +139 -0
  719. package/scaffold/.nvmrc +1 -0
  720. package/scaffold/AGENTS.md +55 -0
  721. package/scaffold/CLAUDE.md +1 -0
  722. package/scaffold/_.gitattributes +4 -0
  723. package/scaffold/_.gitignore +27 -0
  724. package/scaffold/config/omega.json5 +20 -0
  725. package/scaffold/src/assets/css/pages/.gitkeep +0 -0
  726. package/scaffold/src/assets/images/.gitkeep +0 -0
  727. package/scaffold/src/assets/js/pages/.gitkeep +0 -0
  728. package/scaffold/src/pages/.gitkeep +0 -0
  729. package/scaffold/src/pages/example.md.txt +94 -0
  730. package/scaffold/src/service-worker.js +16 -0
  731. package/sw/entry.js +19 -0
  732. package/sw/manager.js +281 -0
  733. package/themes/_template/README.md +115 -0
  734. package/themes/_template/_config.scss +60 -0
  735. package/themes/_template/_theme.js +14 -0
  736. package/themes/_template/_theme.scss +28 -0
  737. package/themes/_template/css/base/_root.scss +19 -0
  738. package/themes/_template/css/components/_components.scss +23 -0
  739. package/themes/base/README.md +5 -0
  740. package/themes/base/_components/data/org-chart/component.html +33 -0
  741. package/themes/base/_components/data/org-chart/component.json5 +33 -0
  742. package/themes/base/_components/data/org-chart/component.scss +183 -0
  743. package/themes/base/_components/heading/masthead/component.html +22 -0
  744. package/themes/base/_components/heading/masthead/component.json5 +30 -0
  745. package/themes/base/_components/heading/section-head/component.html +18 -0
  746. package/themes/base/_components/heading/section-head/component.json5 +27 -0
  747. package/themes/base/_components/pricing/features/component.html +29 -0
  748. package/themes/base/_components/pricing/features/component.json5 +30 -0
  749. package/themes/base/_hero/orbit/index.html +30 -0
  750. package/themes/base/_hero/orbit/script.js +33 -0
  751. package/themes/base/_hero/orbit/style.scss +133 -0
  752. package/themes/base/_includes/admin/sections/sidebar.html +3 -0
  753. package/themes/base/_includes/admin/sections/topbar.html +3 -0
  754. package/themes/base/_includes/backend/sections/sidebar.html +3 -0
  755. package/themes/base/_includes/backend/sections/topbar.html +3 -0
  756. package/themes/base/_includes/frontend/components/connection-card.html +49 -0
  757. package/themes/base/_includes/frontend/components/post-card.html +54 -0
  758. package/themes/base/_includes/frontend/sections/account-connections.html +42 -0
  759. package/themes/base/_includes/frontend/sections/account-section-header.html +11 -0
  760. package/themes/base/_includes/frontend/sections/footer.html +154 -0
  761. package/themes/base/_includes/frontend/sections/nav.html +276 -0
  762. package/themes/base/_includes/global/sections/account.html +72 -0
  763. package/themes/base/_includes/global/sections/app-sidebar.html +248 -0
  764. package/themes/base/_includes/global/sections/app-topbar.html +228 -0
  765. package/themes/base/_includes/global/sections/page-header.html +99 -0
  766. package/themes/base/_layouts/admin/core/minimal-viewport-locked.html +22 -0
  767. package/themes/base/_layouts/admin/core/minimal.html +23 -0
  768. package/themes/base/_layouts/backend/core/base.html +57 -0
  769. package/themes/base/_layouts/backend/core/minimal-viewport-locked.html +32 -0
  770. package/themes/base/_layouts/backend/core/minimal.html +32 -0
  771. package/themes/base/_layouts/backend/pages/dashboard/index.html +230 -0
  772. package/themes/base/_layouts/frontend/core/base.html +31 -0
  773. package/themes/base/_layouts/frontend/core/cover.html +40 -0
  774. package/themes/base/_layouts/frontend/core/minimal.html +47 -0
  775. package/themes/base/_layouts/frontend/pages/404.html +54 -0
  776. package/themes/base/_layouts/frontend/pages/about.html +144 -0
  777. package/themes/base/_layouts/frontend/pages/account/index.html +2104 -0
  778. package/themes/base/_layouts/frontend/pages/alternatives/alternative.html +319 -0
  779. package/themes/base/_layouts/frontend/pages/alternatives/index.html +186 -0
  780. package/themes/base/_layouts/frontend/pages/app.html +142 -0
  781. package/themes/base/_layouts/frontend/pages/auth/reset.html +53 -0
  782. package/themes/base/_layouts/frontend/pages/auth/signin.html +90 -0
  783. package/themes/base/_layouts/frontend/pages/auth/signup.html +141 -0
  784. package/themes/base/_layouts/frontend/pages/auth/token.html +44 -0
  785. package/themes/base/_layouts/frontend/pages/blog/categories/category.html +88 -0
  786. package/themes/base/_layouts/frontend/pages/blog/categories/index.html +88 -0
  787. package/themes/base/_layouts/frontend/pages/blog/index.html +255 -0
  788. package/themes/base/_layouts/frontend/pages/blog/post.html +261 -0
  789. package/themes/base/_layouts/frontend/pages/blog/tags/index.html +84 -0
  790. package/themes/base/_layouts/frontend/pages/blog/tags/tag.html +88 -0
  791. package/themes/base/_layouts/frontend/pages/collection/category.html +72 -0
  792. package/themes/base/_layouts/frontend/pages/collection/document.html +54 -0
  793. package/themes/base/_layouts/frontend/pages/collection/index.html +84 -0
  794. package/themes/base/_layouts/frontend/pages/connections/callback.html +55 -0
  795. package/themes/base/_layouts/frontend/pages/contact.html +214 -0
  796. package/themes/base/_layouts/frontend/pages/download.html +793 -0
  797. package/themes/base/_layouts/frontend/pages/extension/index.html +253 -0
  798. package/themes/base/_layouts/frontend/pages/extension/installed.html +198 -0
  799. package/themes/base/_layouts/frontend/pages/feedback.html +171 -0
  800. package/themes/base/_layouts/frontend/pages/index.html +66 -0
  801. package/themes/base/_layouts/frontend/pages/legal/document.html +78 -0
  802. package/themes/base/_layouts/frontend/pages/payment/checkout.html +459 -0
  803. package/themes/base/_layouts/frontend/pages/payment/confirmation.html +131 -0
  804. package/themes/base/_layouts/frontend/pages/portal/email-preferences.html +104 -0
  805. package/themes/base/_layouts/frontend/pages/pricing.html +596 -0
  806. package/themes/base/_layouts/frontend/pages/status.html +361 -0
  807. package/themes/base/_layouts/frontend/pages/team/index.html +210 -0
  808. package/themes/base/_layouts/frontend/pages/team/member.html +125 -0
  809. package/themes/base/_layouts/frontend/pages/updates/index.html +119 -0
  810. package/themes/base/_layouts/frontend/pages/updates/update.html +118 -0
  811. package/themes/base/_sections/about/hero/section.html +68 -0
  812. package/themes/base/_sections/about/hero/section.json5 +58 -0
  813. package/themes/base/_sections/about/letter/section.html +60 -0
  814. package/themes/base/_sections/about/letter/section.json5 +56 -0
  815. package/themes/base/_sections/about/photo-band/section.html +24 -0
  816. package/themes/base/_sections/about/photo-band/section.json5 +23 -0
  817. package/themes/base/_sections/about/principles/section.html +17 -0
  818. package/themes/base/_sections/about/principles/section.json5 +34 -0
  819. package/themes/base/_sections/about/timeline/section.html +20 -0
  820. package/themes/base/_sections/about/timeline/section.json5 +34 -0
  821. package/themes/base/_sections/marketing/bento/section.html +91 -0
  822. package/themes/base/_sections/marketing/bento/section.json5 +152 -0
  823. package/themes/base/_sections/marketing/cta/section.html +44 -0
  824. package/themes/base/_sections/marketing/cta/section.json5 +47 -0
  825. package/themes/base/_sections/marketing/faq/section.html +52 -0
  826. package/themes/base/_sections/marketing/faq/section.json5 +68 -0
  827. package/themes/base/_sections/marketing/hero/section.html +349 -0
  828. package/themes/base/_sections/marketing/hero/section.json5 +335 -0
  829. package/themes/base/_sections/marketing/newsletter-cta/section.html +38 -0
  830. package/themes/base/_sections/marketing/newsletter-cta/section.js +86 -0
  831. package/themes/base/_sections/marketing/newsletter-cta/section.json5 +29 -0
  832. package/themes/base/_sections/marketing/pricing-cards/section.html +115 -0
  833. package/themes/base/_sections/marketing/pricing-cards/section.json5 +72 -0
  834. package/themes/base/_sections/marketing/product-demo/section.html +62 -0
  835. package/themes/base/_sections/marketing/product-demo/section.js +44 -0
  836. package/themes/base/_sections/marketing/product-demo/section.json5 +52 -0
  837. package/themes/base/_sections/marketing/prose/section.html +22 -0
  838. package/themes/base/_sections/marketing/prose/section.json5 +41 -0
  839. package/themes/base/_sections/marketing/showcase/section.html +35 -0
  840. package/themes/base/_sections/marketing/showcase/section.json5 +52 -0
  841. package/themes/base/_sections/marketing/stats/section.html +32 -0
  842. package/themes/base/_sections/marketing/stats/section.json5 +44 -0
  843. package/themes/base/_sections/marketing/testimonials/section.html +54 -0
  844. package/themes/base/_sections/marketing/testimonials/section.json5 +70 -0
  845. package/themes/base/_sections/marketing/trusted-by/section.html +30 -0
  846. package/themes/base/_sections/marketing/trusted-by/section.json5 +38 -0
  847. package/themes/base/_sections/verts/unit/section.html +7 -0
  848. package/themes/base/_sections/verts/unit/section.js +22 -0
  849. package/themes/base/_sections/verts/unit/section.json5 +24 -0
  850. package/themes/base/_sections/verts/unit/section.scss +24 -0
  851. package/themes/base/_theme.js +13 -0
  852. package/themes/base/_theme.scss +4 -0
  853. package/themes/bootstrap/_theme.js +16 -0
  854. package/themes/bootstrap/_theme.scss +11 -0
  855. package/themes/bootstrap/js/index.esm.js +19 -0
  856. package/themes/bootstrap/js/index.umd.js +34 -0
  857. package/themes/bootstrap/js/src/alert.js +87 -0
  858. package/themes/bootstrap/js/src/base-component.js +86 -0
  859. package/themes/bootstrap/js/src/button.js +72 -0
  860. package/themes/bootstrap/js/src/carousel.js +474 -0
  861. package/themes/bootstrap/js/src/collapse.js +297 -0
  862. package/themes/bootstrap/js/src/dom/data.js +55 -0
  863. package/themes/bootstrap/js/src/dom/event-handler.js +317 -0
  864. package/themes/bootstrap/js/src/dom/manipulator.js +71 -0
  865. package/themes/bootstrap/js/src/dom/selector-engine.js +126 -0
  866. package/themes/bootstrap/js/src/dropdown.js +458 -0
  867. package/themes/bootstrap/js/src/modal.js +378 -0
  868. package/themes/bootstrap/js/src/offcanvas.js +282 -0
  869. package/themes/bootstrap/js/src/popover.js +97 -0
  870. package/themes/bootstrap/js/src/scrollspy.js +296 -0
  871. package/themes/bootstrap/js/src/tab.js +315 -0
  872. package/themes/bootstrap/js/src/toast.js +224 -0
  873. package/themes/bootstrap/js/src/tooltip.js +632 -0
  874. package/themes/bootstrap/js/src/util/backdrop.js +151 -0
  875. package/themes/bootstrap/js/src/util/component-functions.js +35 -0
  876. package/themes/bootstrap/js/src/util/config.js +65 -0
  877. package/themes/bootstrap/js/src/util/focustrap.js +115 -0
  878. package/themes/bootstrap/js/src/util/index.js +306 -0
  879. package/themes/bootstrap/js/src/util/sanitizer.js +117 -0
  880. package/themes/bootstrap/js/src/util/scrollbar.js +114 -0
  881. package/themes/bootstrap/js/src/util/swipe.js +146 -0
  882. package/themes/bootstrap/js/src/util/template-factory.js +160 -0
  883. package/themes/bootstrap/overrides/_accordion.scss +21 -0
  884. package/themes/bootstrap/overrides/_avatars.scss +51 -0
  885. package/themes/bootstrap/overrides/_blog-images.scss +45 -0
  886. package/themes/bootstrap/overrides/_buttons-adaptive.scss +141 -0
  887. package/themes/bootstrap/overrides/_color-shades.scss +25 -0
  888. package/themes/bootstrap/overrides/_cursor-utilities.scss +13 -0
  889. package/themes/bootstrap/overrides/_index.scss +30 -0
  890. package/themes/bootstrap/overrides/_links.scss +26 -0
  891. package/themes/bootstrap/overrides/_soft-colors.scss +99 -0
  892. package/themes/bootstrap/overrides/_spacing-extended.scss +74 -0
  893. package/themes/bootstrap/overrides/_spinners.scss +22 -0
  894. package/themes/bootstrap/overrides/_typography-utilities.scss +40 -0
  895. package/themes/bootstrap/scss/_accordion.scss +153 -0
  896. package/themes/bootstrap/scss/_alert.scss +68 -0
  897. package/themes/bootstrap/scss/_badge.scss +38 -0
  898. package/themes/bootstrap/scss/_breadcrumb.scss +40 -0
  899. package/themes/bootstrap/scss/_button-group.scss +147 -0
  900. package/themes/bootstrap/scss/_buttons.scss +216 -0
  901. package/themes/bootstrap/scss/_card.scss +238 -0
  902. package/themes/bootstrap/scss/_carousel.scss +226 -0
  903. package/themes/bootstrap/scss/_close.scss +66 -0
  904. package/themes/bootstrap/scss/_containers.scss +41 -0
  905. package/themes/bootstrap/scss/_dropdown.scss +250 -0
  906. package/themes/bootstrap/scss/_forms.scss +9 -0
  907. package/themes/bootstrap/scss/_functions.scss +302 -0
  908. package/themes/bootstrap/scss/_grid.scss +39 -0
  909. package/themes/bootstrap/scss/_helpers.scss +12 -0
  910. package/themes/bootstrap/scss/_images.scss +42 -0
  911. package/themes/bootstrap/scss/_list-group.scss +199 -0
  912. package/themes/bootstrap/scss/_maps.scss +174 -0
  913. package/themes/bootstrap/scss/_mixins.scss +42 -0
  914. package/themes/bootstrap/scss/_modal.scss +240 -0
  915. package/themes/bootstrap/scss/_nav.scss +197 -0
  916. package/themes/bootstrap/scss/_navbar.scss +289 -0
  917. package/themes/bootstrap/scss/_offcanvas.scss +147 -0
  918. package/themes/bootstrap/scss/_pagination.scss +109 -0
  919. package/themes/bootstrap/scss/_placeholders.scss +51 -0
  920. package/themes/bootstrap/scss/_popover.scss +196 -0
  921. package/themes/bootstrap/scss/_progress.scss +68 -0
  922. package/themes/bootstrap/scss/_reboot.scss +611 -0
  923. package/themes/bootstrap/scss/_root.scss +187 -0
  924. package/themes/bootstrap/scss/_spinners.scss +85 -0
  925. package/themes/bootstrap/scss/_tables.scss +171 -0
  926. package/themes/bootstrap/scss/_toasts.scss +73 -0
  927. package/themes/bootstrap/scss/_tooltip.scss +119 -0
  928. package/themes/bootstrap/scss/_transitions.scss +27 -0
  929. package/themes/bootstrap/scss/_type.scss +106 -0
  930. package/themes/bootstrap/scss/_utilities.scss +806 -0
  931. package/themes/bootstrap/scss/_variables-dark.scss +102 -0
  932. package/themes/bootstrap/scss/_variables.scss +1753 -0
  933. package/themes/bootstrap/scss/bootstrap-grid.scss +62 -0
  934. package/themes/bootstrap/scss/bootstrap-reboot.scss +10 -0
  935. package/themes/bootstrap/scss/bootstrap-utilities.scss +19 -0
  936. package/themes/bootstrap/scss/bootstrap.scss +52 -0
  937. package/themes/bootstrap/scss/forms/_floating-labels.scss +97 -0
  938. package/themes/bootstrap/scss/forms/_form-check.scss +189 -0
  939. package/themes/bootstrap/scss/forms/_form-control.scss +214 -0
  940. package/themes/bootstrap/scss/forms/_form-range.scss +91 -0
  941. package/themes/bootstrap/scss/forms/_form-select.scss +80 -0
  942. package/themes/bootstrap/scss/forms/_form-text.scss +11 -0
  943. package/themes/bootstrap/scss/forms/_input-group.scss +132 -0
  944. package/themes/bootstrap/scss/forms/_labels.scss +36 -0
  945. package/themes/bootstrap/scss/forms/_validation.scss +12 -0
  946. package/themes/bootstrap/scss/helpers/_clearfix.scss +3 -0
  947. package/themes/bootstrap/scss/helpers/_color-bg.scss +7 -0
  948. package/themes/bootstrap/scss/helpers/_colored-links.scss +30 -0
  949. package/themes/bootstrap/scss/helpers/_focus-ring.scss +5 -0
  950. package/themes/bootstrap/scss/helpers/_icon-link.scss +25 -0
  951. package/themes/bootstrap/scss/helpers/_position.scss +36 -0
  952. package/themes/bootstrap/scss/helpers/_ratio.scss +26 -0
  953. package/themes/bootstrap/scss/helpers/_stacks.scss +15 -0
  954. package/themes/bootstrap/scss/helpers/_stretched-link.scss +15 -0
  955. package/themes/bootstrap/scss/helpers/_text-truncation.scss +7 -0
  956. package/themes/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
  957. package/themes/bootstrap/scss/helpers/_vr.scss +8 -0
  958. package/themes/bootstrap/scss/mixins/_alert.scss +18 -0
  959. package/themes/bootstrap/scss/mixins/_backdrop.scss +14 -0
  960. package/themes/bootstrap/scss/mixins/_banner.scss +7 -0
  961. package/themes/bootstrap/scss/mixins/_border-radius.scss +78 -0
  962. package/themes/bootstrap/scss/mixins/_box-shadow.scss +18 -0
  963. package/themes/bootstrap/scss/mixins/_breakpoints.scss +127 -0
  964. package/themes/bootstrap/scss/mixins/_buttons.scss +70 -0
  965. package/themes/bootstrap/scss/mixins/_caret.scss +69 -0
  966. package/themes/bootstrap/scss/mixins/_clearfix.scss +9 -0
  967. package/themes/bootstrap/scss/mixins/_color-mode.scss +21 -0
  968. package/themes/bootstrap/scss/mixins/_color-scheme.scss +7 -0
  969. package/themes/bootstrap/scss/mixins/_container.scss +11 -0
  970. package/themes/bootstrap/scss/mixins/_deprecate.scss +10 -0
  971. package/themes/bootstrap/scss/mixins/_forms.scss +163 -0
  972. package/themes/bootstrap/scss/mixins/_gradients.scss +47 -0
  973. package/themes/bootstrap/scss/mixins/_grid.scss +151 -0
  974. package/themes/bootstrap/scss/mixins/_image.scss +16 -0
  975. package/themes/bootstrap/scss/mixins/_list-group.scss +26 -0
  976. package/themes/bootstrap/scss/mixins/_lists.scss +7 -0
  977. package/themes/bootstrap/scss/mixins/_pagination.scss +10 -0
  978. package/themes/bootstrap/scss/mixins/_reset-text.scss +17 -0
  979. package/themes/bootstrap/scss/mixins/_resize.scss +6 -0
  980. package/themes/bootstrap/scss/mixins/_table-variants.scss +24 -0
  981. package/themes/bootstrap/scss/mixins/_text-truncate.scss +8 -0
  982. package/themes/bootstrap/scss/mixins/_transition.scss +26 -0
  983. package/themes/bootstrap/scss/mixins/_utilities.scss +97 -0
  984. package/themes/bootstrap/scss/mixins/_visually-hidden.scss +38 -0
  985. package/themes/bootstrap/scss/tests/jasmine.js +16 -0
  986. package/themes/bootstrap/scss/tests/mixins/_auto-import-of-variables-dark.test.scss +7 -0
  987. package/themes/bootstrap/scss/tests/mixins/_color-modes.test.scss +69 -0
  988. package/themes/bootstrap/scss/tests/mixins/_media-query-color-mode-full.test.scss +8 -0
  989. package/themes/bootstrap/scss/tests/mixins/_utilities.test.scss +393 -0
  990. package/themes/bootstrap/scss/tests/sass-true/register.js +14 -0
  991. package/themes/bootstrap/scss/tests/sass-true/runner.js +17 -0
  992. package/themes/bootstrap/scss/tests/utilities/_api.test.scss +75 -0
  993. package/themes/bootstrap/scss/utilities/_api.scss +47 -0
  994. package/themes/bootstrap/scss/vendor/_rfs.scss +348 -0
  995. package/themes/classy/README.md +89 -0
  996. package/themes/classy/_config.scss +245 -0
  997. package/themes/classy/_theme.js +31 -0
  998. package/themes/classy/_theme.scss +57 -0
  999. package/themes/classy/css/app/_panels.scss +183 -0
  1000. package/themes/classy/css/base/_fonts.scss +79 -0
  1001. package/themes/classy/css/base/_no-raise.scss +49 -0
  1002. package/themes/classy/css/base/_root.scss +113 -0
  1003. package/themes/classy/css/base/_typography.scss +154 -0
  1004. package/themes/classy/css/base/_utilities.scss +284 -0
  1005. package/themes/classy/css/components/_alerts.scss +51 -0
  1006. package/themes/classy/css/components/_badges.scss +46 -0
  1007. package/themes/classy/css/components/_buttons.scss +175 -0
  1008. package/themes/classy/css/components/_cards.scss +41 -0
  1009. package/themes/classy/css/components/_dropdowns.scss +48 -0
  1010. package/themes/classy/css/components/_forms.scss +171 -0
  1011. package/themes/classy/css/components/_receipt.scss +45 -0
  1012. package/themes/classy/css/layout/_footer.scss +298 -0
  1013. package/themes/classy/css/layout/_general.scss +209 -0
  1014. package/themes/classy/css/layout/_nav.scss +154 -0
  1015. package/themes/classy/css/layout/_shell.scss +458 -0
  1016. package/themes/classy/css/marketing/_bento.scss +238 -0
  1017. package/themes/classy/css/marketing/_content.scss +1328 -0
  1018. package/themes/classy/css/marketing/_downloads.scss +246 -0
  1019. package/themes/classy/css/marketing/_hero.scss +311 -0
  1020. package/themes/classy/css/marketing/_sections.scss +649 -0
  1021. package/themes/classy/css/pages/404/index.scss +67 -0
  1022. package/themes/classy/css/pages/_auth.scss +235 -0
  1023. package/themes/classy/css/pages/admin/firebase/index.scss +22 -0
  1024. package/themes/classy/css/pages/blog/[slug].scss +56 -0
  1025. package/themes/classy/css/pages/cookies.scss +2 -0
  1026. package/themes/classy/css/pages/dashboard/account/index.scss +31 -0
  1027. package/themes/classy/css/pages/feedback/index.scss +39 -0
  1028. package/themes/classy/css/pages/legal/_document.scss +192 -0
  1029. package/themes/classy/css/pages/payment/checkout/index.scss +585 -0
  1030. package/themes/classy/css/pages/payment/confirmation/index.scss +9 -0
  1031. package/themes/classy/css/pages/privacy.scss +2 -0
  1032. package/themes/classy/css/pages/status/index.scss +164 -0
  1033. package/themes/classy/css/pages/terms.scss +2 -0
  1034. package/themes/classy/css/pages/updates/index.scss +128 -0
  1035. package/themes/classy/fonts/inter-italic-latin-ext.woff2 +0 -0
  1036. package/themes/classy/fonts/inter-italic-latin.woff2 +0 -0
  1037. package/themes/classy/fonts/inter-normal-latin-ext.woff2 +0 -0
  1038. package/themes/classy/fonts/inter-normal-latin.woff2 +0 -0
  1039. package/themes/classy/fonts/newsreader-italic-latin-ext.woff2 +0 -0
  1040. package/themes/classy/fonts/newsreader-italic-latin.woff2 +0 -0
  1041. package/themes/classy/fonts/newsreader-normal-latin-ext.woff2 +0 -0
  1042. package/themes/classy/fonts/newsreader-normal-latin.woff2 +0 -0
  1043. package/themes/classy/js/hero-demo-form.js +56 -0
  1044. package/themes/neobrutalism/README.md +118 -0
  1045. package/themes/neobrutalism/_config.scss +139 -0
  1046. package/themes/neobrutalism/_layouts/frontend/core/base.html +30 -0
  1047. package/themes/neobrutalism/_layouts/frontend/pages/index.html +275 -0
  1048. package/themes/neobrutalism/_theme.js +24 -0
  1049. package/themes/neobrutalism/_theme.scss +61 -0
  1050. package/themes/neobrutalism/css/base/_mixins.scss +46 -0
  1051. package/themes/neobrutalism/css/base/_root.scss +169 -0
  1052. package/themes/neobrutalism/css/base/_typography.scss +61 -0
  1053. package/themes/neobrutalism/css/base/_utilities.scss +89 -0
  1054. package/themes/neobrutalism/css/components/_billing-toggle.scss +74 -0
  1055. package/themes/neobrutalism/css/components/_buttons.scss +148 -0
  1056. package/themes/neobrutalism/css/components/_cards.scss +47 -0
  1057. package/themes/neobrutalism/css/components/_forms.scss +88 -0
  1058. package/themes/neobrutalism/css/components/_marketing.scss +244 -0
  1059. package/themes/neobrutalism/css/layout/_general.scss +213 -0
  1060. package/themes/neobrutalism/css/layout/_navigation.scss +171 -0
  1061. package/themes/neobrutalism/css/pages/index.scss +203 -0
  1062. package/themes/neobrutalism/css/pages/pricing/index.scss +58 -0
  1063. package/themes/neobrutalism/css/pages/test/libraries/layers/index.scss +7 -0
  1064. package/themes/neobrutalism/js/pages/test/libraries/layers/index.js +9 -0
  1065. package/themes/newsflash/README.md +117 -0
  1066. package/themes/newsflash/_components/heading/lede/component.html +14 -0
  1067. package/themes/newsflash/_components/heading/lede/component.json5 +23 -0
  1068. package/themes/newsflash/_components/heading/rule-head/component.html +17 -0
  1069. package/themes/newsflash/_components/heading/rule-head/component.json5 +23 -0
  1070. package/themes/newsflash/_components/news/byline/component.html +12 -0
  1071. package/themes/newsflash/_components/news/byline/component.json5 +18 -0
  1072. package/themes/newsflash/_components/news/story-card/component.html +25 -0
  1073. package/themes/newsflash/_components/news/story-card/component.json5 +21 -0
  1074. package/themes/newsflash/_config.scss +147 -0
  1075. package/themes/newsflash/_layouts/frontend/core/base.html +51 -0
  1076. package/themes/newsflash/_layouts/frontend/pages/blog/index.html +299 -0
  1077. package/themes/newsflash/_layouts/frontend/pages/blog/post.html +235 -0
  1078. package/themes/newsflash/_layouts/frontend/pages/index.html +328 -0
  1079. package/themes/newsflash/_sections/marketing/desks/section.html +39 -0
  1080. package/themes/newsflash/_sections/marketing/desks/section.json5 +49 -0
  1081. package/themes/newsflash/_sections/marketing/newsletter-cta/section.html +103 -0
  1082. package/themes/newsflash/_sections/marketing/newsletter-cta/section.json5 +50 -0
  1083. package/themes/newsflash/_sections/marketing/rundown/section.html +28 -0
  1084. package/themes/newsflash/_sections/marketing/rundown/section.json5 +40 -0
  1085. package/themes/newsflash/_theme.js +24 -0
  1086. package/themes/newsflash/_theme.scss +70 -0
  1087. package/themes/newsflash/css/base/_fonts.scss +80 -0
  1088. package/themes/newsflash/css/base/_mixins.scss +51 -0
  1089. package/themes/newsflash/css/base/_root.scss +257 -0
  1090. package/themes/newsflash/css/base/_typography.scss +49 -0
  1091. package/themes/newsflash/css/base/_utilities.scss +122 -0
  1092. package/themes/newsflash/css/components/_badges.scss +65 -0
  1093. package/themes/newsflash/css/components/_billing-toggle.scss +61 -0
  1094. package/themes/newsflash/css/components/_buttons.scss +139 -0
  1095. package/themes/newsflash/css/components/_cards.scss +47 -0
  1096. package/themes/newsflash/css/components/_editorial.scss +173 -0
  1097. package/themes/newsflash/css/components/_forms.scss +75 -0
  1098. package/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
  1099. package/themes/newsflash/css/components/_panels.scss +156 -0
  1100. package/themes/newsflash/css/components/_ticker.scss +67 -0
  1101. package/themes/newsflash/css/layout/_general.scss +330 -0
  1102. package/themes/newsflash/css/layout/_navigation.scss +168 -0
  1103. package/themes/newsflash/css/pages/404/index.scss +79 -0
  1104. package/themes/newsflash/css/pages/blog/[slug].scss +186 -0
  1105. package/themes/newsflash/css/pages/blog/index.scss +17 -0
  1106. package/themes/newsflash/css/pages/index.scss +157 -0
  1107. package/themes/newsflash/css/pages/pricing/index.scss +55 -0
  1108. package/themes/newsflash/css/pages/test/libraries/layers/index.scss +7 -0
  1109. package/themes/newsflash/fonts/fraunces-italic-latin-ext.woff2 +0 -0
  1110. package/themes/newsflash/fonts/fraunces-italic-latin.woff2 +0 -0
  1111. package/themes/newsflash/fonts/fraunces-normal-latin-ext.woff2 +0 -0
  1112. package/themes/newsflash/fonts/fraunces-normal-latin.woff2 +0 -0
  1113. package/themes/newsflash/fonts/schibsted-grotesk-italic-latin-ext.woff2 +0 -0
  1114. package/themes/newsflash/fonts/schibsted-grotesk-italic-latin.woff2 +0 -0
  1115. package/themes/newsflash/fonts/schibsted-grotesk-normal-latin-ext.woff2 +0 -0
  1116. package/themes/newsflash/fonts/schibsted-grotesk-normal-latin.woff2 +0 -0
  1117. package/themes/newsflash/js/pages/blog/[slug].js +29 -0
  1118. package/themes/newsflash/js/pages/test/libraries/layers/index.js +9 -0
  1119. package/translations/es/pages/404.json +86 -0
  1120. package/translations/es/pages/account.json +36 -0
  1121. package/translations/es/pages/app.json +44 -0
  1122. package/translations/es/pages/cancel.json +36 -0
  1123. package/translations/es/pages/change-password.json +36 -0
  1124. package/translations/es/pages/connections/callback.json +41 -0
  1125. package/translations/es/pages/dashboard/account.json +475 -0
  1126. package/translations/es/pages/dashboard.json +36 -0
  1127. package/translations/es/pages/forgot.json +36 -0
  1128. package/translations/es/pages/join.json +36 -0
  1129. package/translations/es/pages/login.json +36 -0
  1130. package/translations/es/pages/payment/checkout.json +83 -0
  1131. package/translations/es/pages/payment/confirmation.json +62 -0
  1132. package/translations/es/pages/portal/email-preferences.json +46 -0
  1133. package/translations/es/pages/recover.json +36 -0
  1134. package/translations/es/pages/refund.json +36 -0
  1135. package/translations/es/pages/register.json +36 -0
  1136. package/translations/es/pages/reset-password.json +36 -0
  1137. package/translations/es/pages/reset.json +42 -0
  1138. package/translations/es/pages/signin.json +45 -0
  1139. package/translations/es/pages/signup.json +60 -0
  1140. package/translations/es/pages/token.json +39 -0
  1141. package/translations/fa/pages/404.json +86 -0
  1142. package/translations/fa/pages/account.json +36 -0
  1143. package/translations/fa/pages/app.json +44 -0
  1144. package/translations/fa/pages/cancel.json +36 -0
  1145. package/translations/fa/pages/change-password.json +36 -0
  1146. package/translations/fa/pages/connections/callback.json +41 -0
  1147. package/translations/fa/pages/dashboard/account.json +475 -0
  1148. package/translations/fa/pages/dashboard.json +36 -0
  1149. package/translations/fa/pages/forgot.json +36 -0
  1150. package/translations/fa/pages/join.json +36 -0
  1151. package/translations/fa/pages/login.json +36 -0
  1152. package/translations/fa/pages/payment/checkout.json +83 -0
  1153. package/translations/fa/pages/payment/confirmation.json +62 -0
  1154. package/translations/fa/pages/portal/email-preferences.json +46 -0
  1155. package/translations/fa/pages/recover.json +36 -0
  1156. package/translations/fa/pages/refund.json +36 -0
  1157. package/translations/fa/pages/register.json +36 -0
  1158. package/translations/fa/pages/reset-password.json +36 -0
  1159. package/translations/fa/pages/reset.json +42 -0
  1160. package/translations/fa/pages/signin.json +45 -0
  1161. package/translations/fa/pages/signup.json +60 -0
  1162. package/translations/fa/pages/token.json +39 -0
@@ -0,0 +1,1905 @@
1
+ /**
2
+ * Local-development linking for the OMEGA monorepo (master plan §8).
3
+ *
4
+ * The single home for every "work against the local framework source" mechanic:
5
+ * - resolveMonorepoRoot() — find the Omega monorepo on this machine
6
+ * - findBrandRoot() — walk up from a cwd to the brand repo root
7
+ * - discoverTargets() — list the brand's target directories
8
+ * - frameworkPackagesOf() — which @omega.js packages a target depends on
9
+ * - linkLocalPackages() — file:-install those from the monorepo (idempotent)
10
+ * - startMonorepoWatch() — spawn the monorepo's src→dist watch (`npm start`)
11
+ * - acquireWatchLock() / releaseWatchLock() — single-instance guard for the watch
12
+ * - startVendorPropagation() — re-prepare dist-building frameworks when a
13
+ * vendored shared package (devkit, config, account) changes
14
+ * - ensureFreshLocalDist() / freshnessBoot() — check a locally-linked
15
+ * framework's dist (and its @omega.js/* runtime deps') at CLI boot: a link
16
+ * into the monorepo is read-only and stops the boot loudly, any other local
17
+ * checkout is rebuilt and the invocation re-execs once
18
+ *
19
+ * Consumers: `omega dev --local` (@omega.js/web), `mgr i local`
20
+ * (@omega.js/backend, @omega.js/desktop, @omega.js/extension), and the monorepo's
21
+ * own scripts/watch-all.js (lock helpers + vendor propagation).
22
+ */
23
+
24
+ // Libraries
25
+ const fs = require('fs');
26
+ const os = require('os');
27
+ const path = require('path');
28
+ const { spawn, spawnSync } = require('child_process');
29
+ const { createRequire } = require('module');
30
+ const { safeInstall } = require('./safe-install');
31
+ const Logger = require('./logger');
32
+
33
+ // Constants
34
+ const SCOPE = '@omega.js/';
35
+ const DEFAULT_MONOREPO = path.join(os.homedir(), 'Developer', 'Repositories', 'Omega', 'omega');
36
+ const WATCH_LOCK = path.join('.omega', 'dev-watch.lock');
37
+ // The marker a vendor-propagation pass holds while it re-prepares frameworks
38
+ // (#622) — the same pid-stamped shape as the watch lock, so an abandoned one
39
+ // reads as idle.
40
+ const VENDOR_PROPAGATION_LOCK = path.join('.omega', 'vendor-propagation.lock');
41
+ // Where scripts/watch-all.js tees its output (#197). A boot that did NOT spawn
42
+ // the watch reads its state from here — the file is truncated per launch and
43
+ // its header names the pid that owns it.
44
+ const WATCH_LOG = path.join('.temp', 'logs', 'watch-all.log');
45
+ const WATCH_LOG_HEADER = /^# omega log — \S+ — pid=(\d+)$/;
46
+ const WATCH_LOG_POLL_MS = 200;
47
+
48
+ /**
49
+ * Check whether a directory is the Omega monorepo root (package.json named
50
+ * "omega" and a packages/devkit workspace).
51
+ * @param {string} dir - Directory to test.
52
+ * @returns {boolean}
53
+ */
54
+ function isMonorepoRoot(dir) {
55
+ try {
56
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
57
+ return pkg.name === 'omega' && fs.existsSync(path.join(dir, 'packages', 'devkit', 'package.json'));
58
+ } catch (e) {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Resolve the Omega monorepo root on this machine.
65
+ *
66
+ * Order: OMEGA_MONOREPO env override → self-location (walk up from this file's
67
+ * REAL path — covers the workspace copy at <root>/packages/devkit/src, copies
68
+ * vendored into a linked framework's dist/vendor/devkit, and consumers living
69
+ * inside the monorepo; a registry install outside it finds no root and falls
70
+ * through) → the conventional clone location.
71
+ * @returns {string} Absolute monorepo root path.
72
+ * @throws {Error} When no candidate is a valid monorepo root.
73
+ */
74
+ function resolveMonorepoRoot() {
75
+ const fromEnv = process.env.OMEGA_MONOREPO;
76
+ if (fromEnv) {
77
+ if (!isMonorepoRoot(fromEnv)) {
78
+ throw new Error(`OMEGA_MONOREPO is set but is not the Omega monorepo root: ${fromEnv}`);
79
+ }
80
+ return path.resolve(fromEnv);
81
+ }
82
+
83
+ let dir = fs.realpathSync(__dirname);
84
+ while (true) {
85
+ if (isMonorepoRoot(dir)) {
86
+ return dir;
87
+ }
88
+ const parent = path.dirname(dir);
89
+ if (parent === dir) {
90
+ break;
91
+ }
92
+ dir = parent;
93
+ }
94
+
95
+ if (isMonorepoRoot(DEFAULT_MONOREPO)) {
96
+ return DEFAULT_MONOREPO;
97
+ }
98
+
99
+ throw new Error(
100
+ `Could not find the Omega monorepo (looked for ${DEFAULT_MONOREPO}). `
101
+ + `Clone it there or set OMEGA_MONOREPO to its root.`
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Map an @omega.js package name to its monorepo package directory.
107
+ * @param {string} monorepoRoot - Monorepo root path.
108
+ * @param {string} name - Package name (e.g. '@omega.js/client').
109
+ * @returns {string} Absolute path to packages/<short-name>.
110
+ */
111
+ function packageDir(monorepoRoot, name) {
112
+ return path.join(monorepoRoot, 'packages', name.slice(SCOPE.length));
113
+ }
114
+
115
+ /**
116
+ * Check whether a directory contains at least one targets/<name>/package.json.
117
+ * @param {string} dir - Directory to test.
118
+ * @returns {boolean}
119
+ */
120
+ function hasTargets(dir) {
121
+ const targetsDir = path.join(dir, 'targets');
122
+ try {
123
+ return fs.readdirSync(targetsDir).some((entry) => fs.existsSync(path.join(targetsDir, entry, 'package.json')));
124
+ } catch (e) {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Walk up from a starting directory to the brand repo root: the first
131
+ * directory with BOTH a package.json and a targets/<name>/package.json (a brand
132
+ * monorepo). The Omega monorepo itself is never a brand root — a standalone
133
+ * project living inside it (spike, fixture) resolves to itself. Falls back to the
134
+ * nearest package.json (a standalone project), then to the starting directory.
135
+ * @param {string} startDir - Directory to walk up from (usually process.cwd()).
136
+ * @returns {string} Absolute brand root path.
137
+ */
138
+ function findBrandRoot(startDir) {
139
+ let dir = path.resolve(startDir);
140
+ let nearestPackage = null;
141
+
142
+ while (true) {
143
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
144
+ nearestPackage = nearestPackage || dir;
145
+ if (hasTargets(dir) && !isMonorepoRoot(dir)) {
146
+ return dir;
147
+ }
148
+ }
149
+ const parent = path.dirname(dir);
150
+ if (parent === dir) {
151
+ break;
152
+ }
153
+ dir = parent;
154
+ }
155
+
156
+ return nearestPackage || path.resolve(startDir);
157
+ }
158
+
159
+ /**
160
+ * List a brand's target directories: the brand root itself plus every
161
+ * targets/<name> that has a package.json.
162
+ * @param {string} brandRoot - Brand root path.
163
+ * @returns {string[]} Absolute target directory paths.
164
+ */
165
+ function discoverTargets(brandRoot) {
166
+ const targets = [brandRoot];
167
+ const targetsDir = path.join(brandRoot, 'targets');
168
+
169
+ try {
170
+ for (const entry of fs.readdirSync(targetsDir).sort()) {
171
+ const targetDir = path.join(targetsDir, entry);
172
+ if (fs.existsSync(path.join(targetDir, 'package.json'))) {
173
+ targets.push(targetDir);
174
+ }
175
+ }
176
+ } catch (e) {
177
+ // No targets/ directory — standalone brand, the root is the only target
178
+ }
179
+
180
+ return targets;
181
+ }
182
+
183
+ /**
184
+ * Collect a target's @omega.js dependencies from its package.json (the ONE
185
+ * manifest at the target root — scripts + runtime deps; src/dist pillar).
186
+ * @param {string} targetDir - Target directory.
187
+ * @returns {Array<{name: string, spec: string, dev: boolean, dir: string}>}
188
+ */
189
+ function frameworkPackagesOf(targetDir) {
190
+ const entries = [];
191
+
192
+ let pkg;
193
+ try {
194
+ pkg = JSON.parse(fs.readFileSync(path.join(targetDir, 'package.json'), 'utf8'));
195
+ } catch (e) {
196
+ return entries;
197
+ }
198
+ for (const [depKey, dev] of [['dependencies', false], ['devDependencies', true]]) {
199
+ for (const [name, spec] of Object.entries(pkg[depKey] || {})) {
200
+ if (name.startsWith(SCOPE)) {
201
+ entries.push({ name, spec, dev, dir: targetDir });
202
+ }
203
+ }
204
+ }
205
+
206
+ return entries;
207
+ }
208
+
209
+ /**
210
+ * Check whether a dependency is already the monorepo copy: the FIRST
211
+ * node_modules entry walking up from dir (mirroring Node resolution — npm
212
+ * workspaces hoist installs to the brand root) resolves to the target package
213
+ * directory.
214
+ * @param {string} dir - Directory the dependency is declared in.
215
+ * @param {string} name - Package name.
216
+ * @param {string} targetDir - Monorepo package directory.
217
+ * @returns {boolean}
218
+ */
219
+ function isLinkedTo(dir, name, targetDir) {
220
+ let current = path.resolve(dir);
221
+
222
+ while (true) {
223
+ const candidate = path.join(current, 'node_modules', name);
224
+ if (fs.existsSync(candidate)) {
225
+ try {
226
+ return fs.realpathSync(candidate) === fs.realpathSync(targetDir);
227
+ } catch (e) {
228
+ return false;
229
+ }
230
+ }
231
+ const parent = path.dirname(current);
232
+ if (parent === current) {
233
+ return false;
234
+ }
235
+ current = parent;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Rewrite one dependency's spec in a manifest, preserving its dev/prod
241
+ * placement (the same in-place rewrite npm does on --save).
242
+ * @param {string} targetDir - Directory holding the package.json.
243
+ * @param {string} name - Dependency name.
244
+ * @param {boolean} dev - Whether the entry lives in devDependencies.
245
+ * @param {string} spec - New spec value.
246
+ */
247
+ function setDependencySpec(targetDir, name, dev, spec) {
248
+ const manifestPath = path.join(targetDir, 'package.json');
249
+ const pkg = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
250
+ pkg[dev ? 'devDependencies' : 'dependencies'][name] = spec;
251
+ fs.writeFileSync(manifestPath, `${JSON.stringify(pkg, null, 2)}\n`);
252
+ }
253
+
254
+ /**
255
+ * file: spec path — relative to the declaring manifest, forward slashes.
256
+ * Computed from REAL paths on both ends: symlinks resolve physically, so a
257
+ * spec relativized through an aliased view (macOS /var → /private/var, a
258
+ * symlinked brand dir) would count the wrong number of ups and dangle.
259
+ */
260
+ function relativeSpecPath(fromDir, target) {
261
+ return path.relative(fs.realpathSync(fromDir), fs.realpathSync(target)).split(path.sep).join('/');
262
+ }
263
+
264
+ /**
265
+ * file:-install a brand's @omega.js dependencies from the local monorepo.
266
+ *
267
+ * Tree-wide by construction: npm resolves the WHOLE workspace tree on any
268
+ * install anchored in a brand monorepo, so linking one target while a SIBLING
269
+ * target still carries an unpublished registry spec (`@omega.js/backend: *`)
270
+ * 404s before anything links (the cp194 wizard-rehearsal catch — only
271
+ * reachable in a brand OUTSIDE the omega monorepo, the real consumer
272
+ * topology). Every target's @omega.js specs are therefore flipped to file:
273
+ * first (dev/prod placement preserved — the entry is edited in place), then
274
+ * ONE `npm install` materializes the links for the whole tree. Standalone
275
+ * targets degenerate to themselves.
276
+ *
277
+ * Idempotent: dependencies already resolving to the monorepo copy are
278
+ * skipped, and when nothing needs linking no install runs.
279
+ * @param {object} options
280
+ * @param {string} options.dir - Target directory to link from (any target in the brand).
281
+ * @param {string} options.monorepoRoot - Monorepo root path.
282
+ * @param {object} [options.logger] - Logger with log/warn (silent when omitted).
283
+ * @param {boolean} [options.dryRun] - Plan only, write and install nothing.
284
+ * @returns {Promise<Array<{name: string, dir: string, target: string, action: 'link'|'skip'|'missing'}>>}
285
+ */
286
+ async function linkLocalPackages(options) {
287
+ const { dir, monorepoRoot, logger, dryRun } = options;
288
+ const actions = [];
289
+
290
+ const installRoot = findBrandRoot(dir);
291
+ let installNeeded = false;
292
+
293
+ // Manifest snapshots for rollback: every spec flips BEFORE the single
294
+ // install, so an install failure must restore the originals — a
295
+ // half-flipped tree is a quiet git-dirty diff someone could commit.
296
+ const manifestBackups = new Map();
297
+ const backupManifest = (targetDir) => {
298
+ const manifestPath = path.join(targetDir, 'package.json');
299
+ if (!manifestBackups.has(manifestPath)) {
300
+ manifestBackups.set(manifestPath, fs.readFileSync(manifestPath, 'utf8'));
301
+ }
302
+ };
303
+
304
+ for (const targetDir of discoverTargets(installRoot)) {
305
+ for (const entry of frameworkPackagesOf(targetDir)) {
306
+ const target = packageDir(monorepoRoot, entry.name);
307
+
308
+ if (!fs.existsSync(path.join(target, 'package.json'))) {
309
+ actions.push({ name: entry.name, dir: entry.dir, target, action: 'missing' });
310
+ logger && logger.warn(`${entry.name}: no monorepo package at ${target} — skipping`);
311
+ continue;
312
+ }
313
+
314
+ if (isLinkedTo(entry.dir, entry.name, target)) {
315
+ actions.push({ name: entry.name, dir: entry.dir, target, action: 'skip' });
316
+ logger && logger.log(`${entry.name}: already linked to the monorepo`);
317
+ continue;
318
+ }
319
+
320
+ const spec = `file:${relativeSpecPath(entry.dir, target)}`;
321
+ if (entry.spec === spec) {
322
+ // Spec already correct, only the install is missing — no rewrite,
323
+ // so a committed relative spec is never churned to a new layout.
324
+ actions.push({ name: entry.name, dir: entry.dir, target, action: 'link' });
325
+ logger && logger.log(`${entry.name}: spec already file: — installing`);
326
+ installNeeded = true;
327
+ continue;
328
+ }
329
+
330
+ actions.push({ name: entry.name, dir: entry.dir, target, action: 'link' });
331
+ logger && logger.log(`${entry.name}: linking → ${target}`);
332
+ installNeeded = true;
333
+ if (!dryRun) {
334
+ backupManifest(entry.dir);
335
+ setDependencySpec(entry.dir, entry.name, entry.dev, spec);
336
+ }
337
+ }
338
+ }
339
+
340
+ if (installNeeded && !dryRun) {
341
+ try {
342
+ await safeInstall('npm install', { log: true, config: { cwd: installRoot } });
343
+ } catch (error) {
344
+ for (const [manifestPath, contents] of manifestBackups) {
345
+ fs.writeFileSync(manifestPath, contents);
346
+ }
347
+ logger && logger.warn(`install failed — restored ${manifestBackups.size} manifest(s) to their pre-link specs`);
348
+ throw error;
349
+ }
350
+ }
351
+
352
+ return actions;
353
+ }
354
+
355
+ /**
356
+ * Flip a brand tree's @omega.js `file:` specs back to registry specs — the
357
+ * publish-day inverse of linkLocalPackages(). Every file:-spec'd entry in
358
+ * every target manifest becomes the EXACT `<version>` of the CURRENTLY LINKED
359
+ * copy (read from the file: target's own package.json — no monorepo lookup, no
360
+ * registry call, so it works on any machine), then ONE `npm install`
361
+ * re-resolves the tree from the registry. Exact, never a caret (#794): the
362
+ * @omega.js family ships lockstep, so a caret here would let one target float
363
+ * ahead of its siblings on the next release and put two copies of
364
+ * @omega.js/client in one brand. An explicit `range` still wins verbatim — the
365
+ * caller owns that string. Idempotent: registry-spec'd entries are untouched;
366
+ * nothing to flip → no install. Same transactional manifest restore as the
367
+ * linker when the install fails.
368
+ * @param {object} options
369
+ * @param {string} options.dir - Any directory inside the brand.
370
+ * @param {object} [options.logger] - Logger with log/warn (silent when omitted).
371
+ * @param {boolean} [options.dryRun] - Plan only, write and install nothing.
372
+ * @param {string} [options.range] - Explicit spec for every flipped entry, used verbatim (e.g. '0.1.0').
373
+ * @returns {Promise<Array<{name: string, dir: string, spec: string, action: 'flip'|'skip'|'unresolvable'}>>}
374
+ */
375
+ async function restoreRegistrySpecs(options) {
376
+ const { dir, logger, dryRun, range } = options;
377
+ const actions = [];
378
+
379
+ const installRoot = findBrandRoot(dir);
380
+ let installNeeded = false;
381
+
382
+ const manifestBackups = new Map();
383
+ const backupManifest = (targetDir) => {
384
+ const manifestPath = path.join(targetDir, 'package.json');
385
+ if (!manifestBackups.has(manifestPath)) {
386
+ manifestBackups.set(manifestPath, fs.readFileSync(manifestPath, 'utf8'));
387
+ }
388
+ };
389
+
390
+ for (const targetDir of discoverTargets(installRoot)) {
391
+ for (const entry of frameworkPackagesOf(targetDir)) {
392
+ if (!entry.spec.startsWith('file:')) {
393
+ actions.push({ name: entry.name, dir: entry.dir, spec: entry.spec, action: 'skip' });
394
+ continue;
395
+ }
396
+
397
+ let spec = range;
398
+ if (!spec) {
399
+ const target = path.resolve(targetDir, entry.spec.slice('file:'.length));
400
+ try {
401
+ spec = JSON.parse(fs.readFileSync(path.join(target, 'package.json'), 'utf8')).version;
402
+ } catch (e) {
403
+ actions.push({ name: entry.name, dir: entry.dir, spec: entry.spec, action: 'unresolvable' });
404
+ logger && logger.warn(`${entry.name}: cannot read ${target}/package.json — pass { range } or fix the link`);
405
+ continue;
406
+ }
407
+ }
408
+
409
+ actions.push({ name: entry.name, dir: entry.dir, spec, action: 'flip' });
410
+ logger && logger.log(`${entry.name}: ${entry.spec} → ${spec}`);
411
+ installNeeded = true;
412
+ if (!dryRun) {
413
+ backupManifest(entry.dir);
414
+ setDependencySpec(entry.dir, entry.name, entry.dev, spec);
415
+ }
416
+ }
417
+ }
418
+
419
+ if (installNeeded && !dryRun) {
420
+ try {
421
+ await safeInstall('npm install', { log: true, config: { cwd: installRoot } });
422
+ } catch (error) {
423
+ for (const [manifestPath, contents] of manifestBackups) {
424
+ fs.writeFileSync(manifestPath, contents);
425
+ }
426
+ logger && logger.warn(`install failed — restored ${manifestBackups.size} manifest(s) to their file: specs`);
427
+ throw error;
428
+ }
429
+ }
430
+
431
+ return actions;
432
+ }
433
+
434
+ /**
435
+ * Read a pid-stamped lock file and return the live owner pid — clearing the
436
+ * file when its process is gone. Signal 0 delivers nothing; it only asks
437
+ * whether the pid is still there.
438
+ * @param {string} lockPath - Absolute path of the lock file.
439
+ * @returns {number|null} Live pid, or null when unlocked (or stale).
440
+ */
441
+ function readLivePid(lockPath) {
442
+ let pid;
443
+
444
+ try {
445
+ pid = JSON.parse(fs.readFileSync(lockPath, 'utf8')).pid;
446
+ } catch (e) {
447
+ return null;
448
+ }
449
+
450
+ try {
451
+ process.kill(pid, 0);
452
+ return pid;
453
+ } catch (e) {
454
+ // Stale lock — the owning process is gone
455
+ fs.rmSync(lockPath, { force: true });
456
+ return null;
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Read the watch lock and return the live owner pid — clearing the lock when
462
+ * its process is gone.
463
+ * @param {string} monorepoRoot - Monorepo root path.
464
+ * @returns {number|null} Live pid, or null when unlocked.
465
+ */
466
+ function readLiveWatchPid(monorepoRoot) {
467
+ return readLivePid(path.join(monorepoRoot, WATCH_LOCK));
468
+ }
469
+
470
+ /**
471
+ * Whether a vendor-propagation pass is re-preparing the frameworks right now
472
+ * ([#622](https://github.com/Omega-JS-Stack/omega/issues/622)) — a marker
473
+ * whose owner process is gone (a watch killed mid-pass) reads as idle.
474
+ * @param {string} monorepoRoot - Monorepo root path.
475
+ * @returns {boolean}
476
+ */
477
+ function vendorPropagationActive(monorepoRoot) {
478
+ return readLivePid(path.join(monorepoRoot, VENDOR_PROPAGATION_LOCK)) !== null;
479
+ }
480
+
481
+ /**
482
+ * Take the single-instance watch lock for this process.
483
+ * @param {string} monorepoRoot - Monorepo root path.
484
+ * @returns {{acquired: boolean, pid: number}} pid is the lock owner (ours on success).
485
+ */
486
+ function acquireWatchLock(monorepoRoot) {
487
+ const livePid = readLiveWatchPid(monorepoRoot);
488
+ if (livePid) {
489
+ return { acquired: false, pid: livePid };
490
+ }
491
+
492
+ const lockPath = path.join(monorepoRoot, WATCH_LOCK);
493
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
494
+ fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }, null, 2));
495
+ return { acquired: true, pid: process.pid };
496
+ }
497
+
498
+ /**
499
+ * Release the watch lock — only when this process owns it.
500
+ * @param {string} monorepoRoot - Monorepo root path.
501
+ */
502
+ function releaseWatchLock(monorepoRoot) {
503
+ const lockPath = path.join(monorepoRoot, WATCH_LOCK);
504
+ try {
505
+ if (JSON.parse(fs.readFileSync(lockPath, 'utf8')).pid === process.pid) {
506
+ fs.rmSync(lockPath, { force: true });
507
+ }
508
+ } catch (e) {
509
+ // No lock (or unreadable) — nothing to release
510
+ }
511
+ }
512
+
513
+ /**
514
+ * The monorepo a brand's `@omega.js/*` dependencies actually resolve INTO, or
515
+ * null when they come from the registry.
516
+ *
517
+ * Asks resolution, never the manifest: a `file:` spec can be stale, an install
518
+ * can overwrite a link, and npm-workspace hoisting puts the copy at the brand
519
+ * root rather than the target. The first dependency of any target that lands
520
+ * in a `packages/<name>` directory under a monorepo root answers the question
521
+ * — a brand is linked as a whole, never one target at a time ([#587](https://github.com/Omega-JS-Stack/omega/issues/587)).
522
+ * @param {string} brandRoot - Brand root path.
523
+ * @returns {string|null} Absolute monorepo root, or null for a registry install.
524
+ */
525
+ function resolveLinkedMonorepo(brandRoot) {
526
+ for (const targetDir of discoverTargets(brandRoot)) {
527
+ for (const entry of frameworkPackagesOf(targetDir)) {
528
+ const realDir = resolvePackageRealDir(entry.name, targetDir);
529
+ if (!isLocalCheckout(realDir)) {
530
+ continue;
531
+ }
532
+
533
+ // <root>/packages/<name> — the layout packageDir() writes and every
534
+ // link points at.
535
+ const monorepoRoot = path.dirname(path.dirname(realDir));
536
+ if (isMonorepoRoot(monorepoRoot)) {
537
+ return monorepoRoot;
538
+ }
539
+ }
540
+ }
541
+
542
+ return null;
543
+ }
544
+
545
+ // Bound on the wait for a fresh watch's initial prepare: a package that never
546
+ // reports must delay a boot, never strand it.
547
+ const WATCH_READY_TIMEOUT_MS = 120000;
548
+
549
+ /**
550
+ * Readiness tracker for the monorepo watch's initial prepare, fed the watch's
551
+ * own stdout lines.
552
+ *
553
+ * The watch announces its roster (`Watching N packages (src→dist): ...`) and
554
+ * every package prints `'prepare-package': Ready for changes!` behind its
555
+ * `[name]` prefix once its first src→dist pass has landed — so "every dist is
556
+ * written" is exactly "N distinct prefixes have reported"
557
+ * ([#670](https://github.com/Omega-JS-Stack/omega/issues/670)). A package
558
+ * repeats that line on every later rebuild, so DISTINCT prefixes are counted:
559
+ * one chatty package can never stand in for one still preparing.
560
+ * @returns {{push: function, isReady: function, pending: function}} push(line)
561
+ * consumes one stdout line; pending() names the roster entries yet to report.
562
+ */
563
+ function createWatchReadyTracker() {
564
+ let expected = null;
565
+ let roster = [];
566
+ const reported = new Set();
567
+
568
+ return {
569
+ push(line) {
570
+ const text = line.trim();
571
+
572
+ // The shared-package roster line (`Watching N shared packages ...`)
573
+ // is a different watch with no ready lines — it must not set N.
574
+ const rosterMatch = /^Watching (\d+) packages \(src→dist\): (.+)$/.exec(text);
575
+ if (rosterMatch) {
576
+ expected = Number(rosterMatch[1]);
577
+ roster = rosterMatch[2].split(',').map((name) => name.trim());
578
+ return;
579
+ }
580
+
581
+ const readyMatch = /^\[([^\]]+)\].*'prepare-package': Ready for changes!$/.exec(text);
582
+ if (readyMatch) {
583
+ reported.add(readyMatch[1].trim());
584
+ }
585
+ },
586
+ isReady() {
587
+ return expected !== null && reported.size >= expected;
588
+ },
589
+ pending() {
590
+ return roster.filter((name) => !reported.has(name));
591
+ },
592
+ };
593
+ }
594
+
595
+ /**
596
+ * Readiness for a watch this process did NOT start
597
+ * ([#622](https://github.com/Omega-JS-Stack/omega/issues/622)).
598
+ *
599
+ * A boot that reuses a running watch has no child stdout to read, and skipping
600
+ * the gate is what let a leg require a framework dist mid-purge: the watch may
601
+ * be seconds old with its initial prepare still landing, or a vendor
602
+ * propagation pass may be re-preparing every framework right now. Both are
603
+ * readable from OUTSIDE the watch — its tee'd log (`.temp/logs/watch-all.log`)
604
+ * fed through the same tracker the fresh branch uses, plus the propagation
605
+ * marker — so the wait costs a long-idle watch nothing (every package has
606
+ * already printed its ready line) and holds a boot only while something is
607
+ * genuinely in flight.
608
+ *
609
+ * The log is only believed when its header pid IS the lock owner's: watch-all
610
+ * truncates the file at launch, so a header naming anyone else is a log this
611
+ * watch never wrote and there is nothing to gate on. Same three outcomes as the
612
+ * fresh branch, and it never rejects: 'ready', 'exit' (the watch died while we
613
+ * waited — nothing is coming) or 'timeout' after WATCH_READY_TIMEOUT_MS.
614
+ * @param {object} options
615
+ * @param {string} options.monorepoRoot - Monorepo root path.
616
+ * @param {number} options.pid - The running watch's pid (the lock owner).
617
+ * @param {object} [options.logger] - Logger with log (silent when omitted); warnings carry the watch's own tag.
618
+ * @returns {Promise<'ready'|'exit'|'timeout'>}
619
+ */
620
+ function awaitRunningWatchReady(options) {
621
+ const { monorepoRoot, pid, logger } = options;
622
+
623
+ let fd;
624
+ try {
625
+ fd = fs.openSync(path.join(monorepoRoot, WATCH_LOG), 'r');
626
+ } catch (e) {
627
+ // No log to read (a CI run skips the tee) — a boot never pays the timeout
628
+ // for a file that does not exist.
629
+ return Promise.resolve('ready');
630
+ }
631
+
632
+ let tracker = createWatchReadyTracker();
633
+ let headerPid = null;
634
+ let offset = 0;
635
+ let carry = '';
636
+ const drain = () => {
637
+ const size = fs.fstatSync(fd).size;
638
+ // A relaunch truncates the log — re-read the new one from its header, and
639
+ // keep nothing the old file said: a fresh tracker, and headerPid back to
640
+ // null ("no header read yet"), so the new log's own header is what decides
641
+ // whether any of this belongs to the watch this boot waits on.
642
+ if (size < offset) {
643
+ offset = 0;
644
+ carry = '';
645
+ tracker = createWatchReadyTracker();
646
+ headerPid = null;
647
+ }
648
+ if (size === offset) {
649
+ return;
650
+ }
651
+
652
+ const buffer = Buffer.alloc(size - offset);
653
+ // readSync may return short — advance by what was READ, never by the size
654
+ // stat'd above, or the gap becomes garbage bytes appended to carry.
655
+ const read = fs.readSync(fd, buffer, 0, buffer.length, offset);
656
+ offset += read;
657
+ carry += buffer.toString('utf8', 0, read);
658
+ const lines = carry.split('\n');
659
+ carry = lines.pop();
660
+ for (const line of lines) {
661
+ const header = WATCH_LOG_HEADER.exec(line.trim());
662
+ if (header) {
663
+ headerPid = Number(header[1]);
664
+ }
665
+ tracker.push(line);
666
+ }
667
+ };
668
+ const settled = () => tracker.isReady() && !vendorPropagationActive(monorepoRoot);
669
+
670
+ drain();
671
+ if (headerPid !== pid) {
672
+ fs.closeSync(fd);
673
+ logger && logger.log(`Monorepo watch (pid ${pid}) writes no log this boot can read — booting without a readiness wait`);
674
+ return Promise.resolve('ready');
675
+ }
676
+ if (settled()) {
677
+ fs.closeSync(fd);
678
+ return Promise.resolve('ready');
679
+ }
680
+
681
+ const stragglers = tracker.pending();
682
+ logger && logger.log(`Monorepo watch (pid ${pid}) is mid-prepare${stragglers.length > 0 ? ` (${stragglers.join(', ')})` : ''} — waiting for it to land`);
683
+ // Warnings carry the watch's own identity, the way the spawned branch's do —
684
+ // a caller's logger is only guaranteed to have log().
685
+ const watchLogger = new Logger('watch');
686
+
687
+ return new Promise((resolve) => {
688
+ // Deliberately NOT unref'd: while the gate holds, this wait is the only
689
+ // thing the boot is doing, and an unref'd timer would let the process fall
690
+ // out from under it.
691
+ const poll = setInterval(() => {
692
+ drain();
693
+ // Checked BEFORE the verdict: a relaunched log clears the header, and
694
+ // until the new one names a pid there is nothing to judge (truncate and
695
+ // header write are two steps). Once it names a pid that is NOT the one
696
+ // this boot waits on, every line in the file belongs to a different
697
+ // watch — unreadable state, so the gate settles the way it does for a
698
+ // watch that died, rather than settling 'ready' off another's lines.
699
+ if (headerPid !== null && headerPid !== pid) {
700
+ watchLogger.warn(`Watch (pid ${pid}) no longer owns its log (now written by pid ${headerPid}) — continuing anyway`);
701
+ finish('exit');
702
+ return;
703
+ }
704
+ if (settled()) {
705
+ finish('ready');
706
+ return;
707
+ }
708
+ try {
709
+ process.kill(pid, 0);
710
+ } catch (e) {
711
+ // The watch died mid-wait — that pass is never landing, and the boot's
712
+ // own freshness sweep already ruled on the dists.
713
+ watchLogger.warn(`Watch (pid ${pid}) exited before its prepare landed — continuing anyway`);
714
+ finish('exit');
715
+ }
716
+ }, WATCH_LOG_POLL_MS);
717
+ const deadline = setTimeout(() => {
718
+ const pendingNames = tracker.pending();
719
+ watchLogger.warn(`Watch still preparing after ${WATCH_READY_TIMEOUT_MS / 1000}s${pendingNames.length > 0 ? ` (${pendingNames.join(', ')})` : ''} — continuing anyway`);
720
+ finish('timeout');
721
+ }, WATCH_READY_TIMEOUT_MS);
722
+
723
+ // Declared after the timers it clears — every path that calls it runs on a
724
+ // later tick, so the bindings are live by then.
725
+ const finish = (outcome) => {
726
+ clearInterval(poll);
727
+ clearTimeout(deadline);
728
+ fs.closeSync(fd);
729
+ resolve(outcome);
730
+ };
731
+ });
732
+ }
733
+
734
+ /**
735
+ * Spawn the monorepo's src→dist watch (`npm start` at the monorepo root) as a
736
+ * SESSION-SCOPED child, unless one is already running (lock held by a live
737
+ * process — the watch script itself takes the lock).
738
+ *
739
+ * Session-scoped means the watch dies with the process that started it: the
740
+ * `exit` hook below fires on every ordinary end, including the deliberate
741
+ * `process.exit()` a caller's own Ctrl-C shutdown makes. Callers keep their own
742
+ * signal policy (a dev server exits on SIGINT, the brand-root orchestrator
743
+ * stops its legs first) — the watch's death is wired HERE, once, so no caller
744
+ * re-implements it ([#587](https://github.com/Omega-JS-Stack/omega/issues/587)).
745
+ *
746
+ * A FRESH watch rewrites every package's dist as it runs its initial prepare
747
+ * pass, so `ready` gates that pass: a caller awaits it before booting anything
748
+ * that loads those dists ([#670](https://github.com/Omega-JS-Stack/omega/issues/670)).
749
+ * It resolves when every watched package has reported ready, when the watch
750
+ * child exits before reporting, or after WATCH_READY_TIMEOUT_MS with one
751
+ * warning naming the stragglers — it never rejects, and resolves with which of
752
+ * the three settled it ('ready' | 'exit' | 'timeout') so a caller only claims
753
+ * success on 'ready'. An ALREADY-RUNNING watch gates too — the same three
754
+ * outcomes, read from its log instead of a child's stdout
755
+ * (awaitRunningWatchReady, [#622](https://github.com/Omega-JS-Stack/omega/issues/622)).
756
+ * @param {object} options
757
+ * @param {string} options.monorepoRoot - Monorepo root path.
758
+ * @param {object} [options.logger] - Logger with log (silent when omitted).
759
+ * @returns {{alreadyRunning: boolean, pid: number|null, child: object|null, ready: Promise<'ready'|'exit'|'timeout'>}}
760
+ */
761
+ function startMonorepoWatch(options) {
762
+ const { monorepoRoot, logger } = options;
763
+
764
+ const livePid = readLiveWatchPid(monorepoRoot);
765
+ if (livePid) {
766
+ logger && logger.log(`Monorepo watch already running (pid ${livePid}) — leaving it be`);
767
+ return { alreadyRunning: true, pid: livePid, child: null, ready: awaitRunningWatchReady({ monorepoRoot, pid: livePid, logger }) };
768
+ }
769
+
770
+ const child = spawn('npm', ['start'], { cwd: monorepoRoot, stdio: ['ignore', 'pipe', 'pipe'] });
771
+ // Forwarded child output keeps its OWN identity — these lines come from the
772
+ // monorepo watch, not from whatever command spawned it, so they stamp
773
+ // `[@omega.js/<host>:watch]` instead of borrowing the caller's logger (#12).
774
+ const watchLogger = new Logger('watch');
775
+ const forward = (stream) => {
776
+ stream.setEncoding('utf8');
777
+ stream.on('data', (chunk) => {
778
+ for (const line of chunk.split('\n')) {
779
+ if (line.trim()) {
780
+ watchLogger.log(line);
781
+ }
782
+ }
783
+ });
784
+ };
785
+ forward(child.stdout);
786
+ forward(child.stderr);
787
+
788
+ const tracker = createWatchReadyTracker();
789
+ let settleReady;
790
+ const ready = new Promise((resolve) => {
791
+ settleReady = resolve;
792
+ });
793
+ const readyTimer = setTimeout(() => {
794
+ const stragglers = tracker.pending();
795
+ watchLogger.warn(`Initial prepare still running after ${WATCH_READY_TIMEOUT_MS / 1000}s${stragglers.length > 0 ? ` (${stragglers.join(', ')})` : ''} — continuing anyway`);
796
+ settleReady('timeout');
797
+ }, WATCH_READY_TIMEOUT_MS);
798
+ // Never the reason a process stays alive: the watch child already holds the
799
+ // loop open for as long as the session wants it.
800
+ readyTimer.unref();
801
+
802
+ // Its OWN line buffering, beside the forwarding above: readiness matches a
803
+ // line's tail and a chunk boundary can split one. A smeared LOG line is
804
+ // cosmetic, where a missed ready line would hold a boot for the full timeout.
805
+ let pending = '';
806
+ child.stdout.on('data', (chunk) => {
807
+ pending += chunk;
808
+ const lines = pending.split('\n');
809
+ pending = lines.pop();
810
+ lines.forEach((line) => tracker.push(line));
811
+ if (tracker.isReady()) {
812
+ clearTimeout(readyTimer);
813
+ settleReady('ready');
814
+ }
815
+ });
816
+
817
+ // A watch that dies before it reports — a lost lock race, a spawn that never
818
+ // ran — has no pass left to wait for, so its exit settles the wait instead of
819
+ // parking a boot on the full timeout.
820
+ child.on('exit', () => {
821
+ clearTimeout(readyTimer);
822
+ settleReady('exit');
823
+ });
824
+
825
+ // Session-scoped: no orphaned watcher outlives the session that spawned it.
826
+ process.on('exit', () => {
827
+ try {
828
+ child.kill('SIGTERM');
829
+ } catch (e) {
830
+ // Already gone
831
+ }
832
+ });
833
+
834
+ logger && logger.log(`Monorepo watch started (pid ${child.pid}) — src→dist rebuilds are live`);
835
+
836
+ return { alreadyRunning: false, pid: child.pid, child, ready };
837
+ }
838
+
839
+ /**
840
+ * Propagate vendored shared-package edits to the frameworks that embed them:
841
+ * watch each vendorable package's src/ and re-run `npm run prepare` in the
842
+ * dependents that actually vendor a changed package. A framework's own
843
+ * prepare:watch only sees its OWN src, while the devkit/config/account copies
844
+ * inside its dist/vendor/* refresh only on a full prepare — without this, a
845
+ * shared-package edit strands every dist-running framework on stale vendored
846
+ * code until a manual rebuild.
847
+ *
848
+ * Each pass is SCOPED (a dependent whose dist/vendor lacks every changed
849
+ * package is skipped; a dependent with no vendor tree yet always runs — first
850
+ * build) and CONCURRENT (independent prepares, no ordering contract). Edits
851
+ * inside the debounce window fold into one pass; edits landing mid-pass queue
852
+ * exactly one follow-up pass. A dependent's prepare failing is logged and
853
+ * never stops the rest of the pass.
854
+ *
855
+ * A running pass holds `.omega/vendor-propagation.lock` (pid inside) for its
856
+ * whole life, so a brand booting on these dists can wait it out instead of
857
+ * requiring a framework mid-purge
858
+ * ([#622](https://github.com/Omega-JS-Stack/omega/issues/622)).
859
+ * @param {object} options
860
+ * @param {string} options.packagesDir - The monorepo's packages/ directory.
861
+ * @param {string[]} options.packages - Vendorable package dir names to watch (missing src/ dirs are skipped).
862
+ * @param {Array<{name: string, dir: string}>} options.dependents - Packages eligible for re-prepare.
863
+ * @param {function} [options.runPrepare] - (dependent) => Promise; defaults to spawning `npm run prepare` in dependent.dir.
864
+ * @param {function} [options.log] - Line logger (silent when omitted).
865
+ * @param {number} [options.debounceMs] - Quiet window before a pass (default 400).
866
+ * @returns {{watched: string[], poke: function, close: function}} poke(name)
867
+ * injects a change event without the fs (test seam — the fs watchers call
868
+ * the same path).
869
+ */
870
+ function startVendorPropagation(options) {
871
+ const { packagesDir, packages, dependents, log = () => {}, debounceMs = 400 } = options;
872
+
873
+ // This prepare takes the dependent's heal lock for the child's whole life:
874
+ // it writes the same dist a booting CLI's heal rebuilds, so the two must
875
+ // never run at once. The wait is async — the watcher keeps serving its other
876
+ // watches while a peer's prepare finishes.
877
+ const runPrepare = options.runPrepare || ((dependent) => acquireHealLock(dependent.dir).then((release) => new Promise((resolve) => {
878
+ const done = () => {
879
+ release();
880
+ resolve();
881
+ };
882
+ const child = spawn('npm', ['run', 'prepare'], { cwd: dependent.dir, stdio: ['ignore', 'pipe', 'pipe'] });
883
+
884
+ // Quiet on success — surface output only when the prepare fails
885
+ let output = '';
886
+ child.stdout.on('data', (chunk) => { output += chunk; });
887
+ child.stderr.on('data', (chunk) => { output += chunk; });
888
+
889
+ child.on('error', (error) => {
890
+ log(`prepare failed to spawn in ${dependent.name}: ${error.message}`);
891
+ done();
892
+ });
893
+ child.on('exit', (code) => {
894
+ if (code !== 0) {
895
+ log(`prepare exited ${code} in ${dependent.name}:\n${output.trim()}`);
896
+ } else {
897
+ log(`re-prepared ${dependent.name}`);
898
+ }
899
+ done();
900
+ });
901
+ })));
902
+
903
+ // The monorepo root by construction: packagesDir IS <root>/packages, so the
904
+ // marker lands beside the watch lock, where a booting brand looks for it.
905
+ const markerPath = path.join(path.dirname(packagesDir), VENDOR_PROPAGATION_LOCK);
906
+ const holdMarker = () => {
907
+ fs.mkdirSync(path.dirname(markerPath), { recursive: true });
908
+ fs.writeFileSync(markerPath, JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }, null, 2));
909
+ };
910
+ const releaseMarker = () => fs.rmSync(markerPath, { force: true });
911
+
912
+ let timer = null;
913
+ let running = false;
914
+ let pending = false;
915
+ let closed = false;
916
+ const dirty = new Set();
917
+
918
+ const pass = async () => {
919
+ try {
920
+ running = true;
921
+ // A pass DELETES each dependent's dist before recopying it, so a target
922
+ // booting into one requires a half-purged framework (#622). The marker
923
+ // says "a purge is in flight" to anything that gates on it (a brand
924
+ // boot's readiness wait) — pid-stamped, so a watch killed mid-pass
925
+ // leaves nothing a later boot honors. Held INSIDE the try so the finally
926
+ // owns the whole lifecycle: no throw can leave the flag or the marker up.
927
+ holdMarker();
928
+ do {
929
+ pending = false;
930
+ const changed = [...dirty];
931
+ dirty.clear();
932
+
933
+ // A spurious trigger with nothing dirty (fs watchers can replay stale
934
+ // events under load) is a no-op, never a full re-prepare
935
+ if (changed.length === 0) {
936
+ continue;
937
+ }
938
+
939
+ // Scope: skip dependents whose dist/vendor embeds none of the changed
940
+ // packages (a missing vendor tree means not-yet-prepared — always run)
941
+ const affected = dependents.filter((dependent) => {
942
+ const vendorRoot = path.join(dependent.dir, 'dist', 'vendor');
943
+ return !fs.existsSync(vendorRoot)
944
+ || changed.some((name) => fs.existsSync(path.join(vendorRoot, name)));
945
+ });
946
+ const skipped = dependents.length - affected.length;
947
+ log(`${changed.join(', ')} changed — re-preparing ${affected.map((d) => d.name).join(', ') || '(none)'}${skipped > 0 ? ` (${skipped} unaffected)` : ''}`);
948
+
949
+ if (closed) {
950
+ break;
951
+ }
952
+ await Promise.all(affected.map((dependent) =>
953
+ Promise.resolve()
954
+ .then(() => runPrepare(dependent))
955
+ .catch((error) => log(`prepare failed in ${dependent.name}: ${error.message}`))
956
+ ));
957
+ } while (pending && !closed);
958
+ } finally {
959
+ releaseMarker();
960
+ running = false;
961
+ }
962
+ };
963
+
964
+ const trigger = () => {
965
+ if (closed) {
966
+ return;
967
+ }
968
+ if (running) {
969
+ pending = true;
970
+ return;
971
+ }
972
+ // Floated on purpose, and un-caught on purpose: prepare failures are
973
+ // already handled per-dependent, so a rejection here means the marker
974
+ // machinery itself broke — a watch that cannot say "a purge is in flight"
975
+ // crashes loudly rather than purge dists behind a gate no boot can see.
976
+ pass();
977
+ };
978
+
979
+ const onSourceEvent = (name) => {
980
+ if (closed) {
981
+ return;
982
+ }
983
+ dirty.add(name);
984
+ clearTimeout(timer);
985
+ timer = setTimeout(trigger, debounceMs);
986
+ };
987
+
988
+ const watchers = [];
989
+ const watched = [];
990
+ for (const name of packages) {
991
+ const srcDir = path.join(packagesDir, name, 'src');
992
+ if (!fs.existsSync(srcDir)) {
993
+ continue;
994
+ }
995
+
996
+ watchers.push(fs.watch(srcDir, { recursive: true }, () => onSourceEvent(name)));
997
+ watched.push(name);
998
+ }
999
+
1000
+ return {
1001
+ watched,
1002
+ poke: onSourceEvent,
1003
+ close: () => {
1004
+ closed = true;
1005
+ clearTimeout(timer);
1006
+ watchers.forEach((watcher) => watcher.close());
1007
+ },
1008
+ };
1009
+ }
1010
+
1011
+ // The vendorable private packages folded into framework dists. Hardcoded here
1012
+ // because this module must stay stdlib-only — tools/vendor.js
1013
+ // VENDORABLE_PACKAGES is the SSOT (a devkit test pins the two lists equal).
1014
+ const FRESHNESS_VENDORABLES = ['devkit', 'config', 'account', 'template-kit', 'analytics', 'monitoring'];
1015
+
1016
+ // Directory names the freshness scan never descends into.
1017
+ const FRESHNESS_SKIP_DIRS = new Set(['node_modules', '.temp', 'dist']);
1018
+
1019
+ // Dist paths prepare legitimately generates with no src counterpart, so their
1020
+ // presence is never "a src file was deleted": the vendor hook's module copies
1021
+ // (dist/vendor/**). Nothing else — vendor-docs writes to the package ROOT
1022
+ // docs/ and prepare-package rewrites the ROOT package.json, so a dist/docs or
1023
+ // dist/package.json IS a leftover. A host's declared `omega.vendorAssets`
1024
+ // destinations join the set per package (see allowedDistExtras) — that is how
1025
+ // desktop/extension end up with a dist/assets tree the web package owns.
1026
+ const DIST_EXTRAS = ['vendor'];
1027
+
1028
+ // The one dist subtree the orphan scan skips entirely: a package's self-tests
1029
+ // SEED runtime state into dist/test/fixtures/ (backend writes its fixture
1030
+ // project's firestore.rules and service-account.json there). Nothing in it can
1031
+ // shadow a consumer require, and the exemption survives a crashed run where a
1032
+ // teardown would not (#352). Files there WITH a src counterpart still age.
1033
+ const DIST_FIXTURE_STATE = 'test/fixtures/';
1034
+
1035
+ // The per-package heal mutex (mkdir-as-mutex, owner pid inside) — .omega/ is
1036
+ // gitignored monorepo-wide, so the lock never dirties a package.
1037
+ const HEAL_LOCK = path.join('.omega', 'heal.lock');
1038
+ // A prepare legitimately holds the lock for minutes, so the loser waits long
1039
+ // and only ever proceeds unlocked as a last resort — a heal must never fail
1040
+ // over its own bookkeeping.
1041
+ const HEAL_LOCK_WAIT_MS = 120000;
1042
+ const HEAL_LOCK_POLL_MS = 50;
1043
+ // Grace for the window between mkdir and the owner file landing: a lock with no
1044
+ // readable owner is only abandoned once it is older than this.
1045
+ const HEAL_LOCK_ORPHAN_MS = 5000;
1046
+
1047
+ // Bounded grace for an in-flight watcher copy before the boot heals anyway.
1048
+ const WATCH_GRACE_MS = 2000;
1049
+ const WATCH_POLL_MS = 100;
1050
+
1051
+ /**
1052
+ * Synchronous wait — this whole path is sync by design (it runs before a CLI's
1053
+ * first await, and freshnessBoot may re-exec the process).
1054
+ * @param {number} ms - Milliseconds to block.
1055
+ */
1056
+ function sleepSync(ms) {
1057
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1058
+ }
1059
+
1060
+ /**
1061
+ * Newest mtime (ms) of any file or directory under dir, recursive — skipping
1062
+ * node_modules/.temp/dist and never following symlinks. Directory mtimes are
1063
+ * counted too, so a deletion (which touches only the parent dir) still reads
1064
+ * as a change. Missing dir → 0.
1065
+ * @param {string} dir - Directory to scan.
1066
+ * @returns {number} Newest mtimeMs, or 0 when nothing exists.
1067
+ */
1068
+ function newestMtimeUnder(dir) {
1069
+ let newest = 0;
1070
+ const queue = [dir];
1071
+
1072
+ while (queue.length > 0) {
1073
+ const current = queue.pop();
1074
+ let entries;
1075
+ try {
1076
+ entries = fs.readdirSync(current, { withFileTypes: true });
1077
+ newest = Math.max(newest, fs.statSync(current).mtimeMs);
1078
+ } catch (e) {
1079
+ continue;
1080
+ }
1081
+ for (const entry of entries) {
1082
+ if (entry.isSymbolicLink()) {
1083
+ continue;
1084
+ }
1085
+ const abs = path.join(current, entry.name);
1086
+ if (entry.isDirectory()) {
1087
+ if (!FRESHNESS_SKIP_DIRS.has(entry.name)) {
1088
+ queue.push(abs);
1089
+ }
1090
+ continue;
1091
+ }
1092
+ if (entry.isFile()) {
1093
+ try {
1094
+ newest = Math.max(newest, fs.statSync(abs).mtimeMs);
1095
+ } catch (e) {
1096
+ // Raced deletion — skip
1097
+ }
1098
+ }
1099
+ }
1100
+ }
1101
+
1102
+ return newest;
1103
+ }
1104
+
1105
+ /**
1106
+ * Newest mtime (ms) of a path that may be a FILE or a directory — a declared
1107
+ * `omega.vendorAssets` entry names either shape. Missing path → 0.
1108
+ * @param {string} target - File or directory to measure.
1109
+ * @returns {number} Newest mtimeMs, or 0 when nothing exists.
1110
+ */
1111
+ function newestMtimeOf(target) {
1112
+ let stat;
1113
+ try {
1114
+ stat = fs.statSync(target);
1115
+ } catch (e) {
1116
+ return 0;
1117
+ }
1118
+
1119
+ return stat.isDirectory() ? newestMtimeUnder(target) : stat.mtimeMs;
1120
+ }
1121
+
1122
+ /**
1123
+ * Every file under dir, keyed by its dir-relative POSIX path — skipping
1124
+ * node_modules/.temp/dist and never following symlinks (a framework dist can
1125
+ * carry a circular self-link fixture). Missing dir → empty map.
1126
+ * @param {string} dir - Directory to scan.
1127
+ * @returns {Map<string, number>} relative path → mtimeMs.
1128
+ */
1129
+ function filesUnder(dir) {
1130
+ const files = new Map();
1131
+ const queue = [''];
1132
+
1133
+ while (queue.length > 0) {
1134
+ const relative = queue.pop();
1135
+ let entries;
1136
+ try {
1137
+ entries = fs.readdirSync(path.join(dir, relative), { withFileTypes: true });
1138
+ } catch (e) {
1139
+ continue;
1140
+ }
1141
+ for (const entry of entries) {
1142
+ if (entry.isSymbolicLink()) {
1143
+ continue;
1144
+ }
1145
+ const child = relative ? `${relative}/${entry.name}` : entry.name;
1146
+ if (entry.isDirectory()) {
1147
+ if (!FRESHNESS_SKIP_DIRS.has(entry.name)) {
1148
+ queue.push(child);
1149
+ }
1150
+ continue;
1151
+ }
1152
+ if (entry.isFile()) {
1153
+ try {
1154
+ files.set(child, fs.statSync(path.join(dir, child)).mtimeMs);
1155
+ } catch (e) {
1156
+ // Raced deletion — skip
1157
+ }
1158
+ }
1159
+ }
1160
+ }
1161
+
1162
+ return files;
1163
+ }
1164
+
1165
+ /**
1166
+ * The dist paths a prepare of THIS package generates without a src counterpart:
1167
+ * the standing set (`dist/vendor/**`) plus every `omega.vendorAssets`
1168
+ * destination the host declares (each is a dist-relative file or directory the
1169
+ * vendor hook copies from another package).
1170
+ * @param {object|null} pkg - The package's manifest.
1171
+ * @returns {string[]} Dist-relative POSIX paths (files or directory roots).
1172
+ */
1173
+ function allowedDistExtras(pkg) {
1174
+ const declared = ((pkg && pkg.omega && pkg.omega.vendorAssets) || [])
1175
+ .map((entry) => entry && entry.to)
1176
+ .filter(Boolean)
1177
+ .map((to) => to.split(path.sep).join('/').replace(/^\.\//, '').replace(/\/+$/, ''));
1178
+
1179
+ return DIST_EXTRAS.concat(declared);
1180
+ }
1181
+
1182
+ /**
1183
+ * Per-file staleness evidence for a locally-linked package's dist.
1184
+ *
1185
+ * A whole-tree newest-mtime compare is not enough: prepare's after-hook writes
1186
+ * into dist too (vendored modules, declared assets), so ANY dist write
1187
+ * after a src edit made the tree read "fresh" while individual dist files were
1188
+ * stale or missing outright (the 2026-08-05 incident). Evidence instead: every
1189
+ * src file must exist at its mapped dist path with an mtime at least as new, no
1190
+ * dist file may lack a src counterpart (a deleted source) unless prepare
1191
+ * generates it, and — inside the monorepo — each embedded dist/vendor/<name>
1192
+ * copy must be newer than that private package's src, and each declared
1193
+ * `omega.vendorAssets` destination newer than the sibling package's source it
1194
+ * was copied from (both kinds of copy only refresh on a full prepare).
1195
+ * @param {object} options
1196
+ * @param {string} options.srcDir - The package's src directory.
1197
+ * @param {string} options.distDir - The package's dist directory.
1198
+ * @param {object|null} options.pkg - The package's manifest.
1199
+ * @param {string} options.monorepoRoot - Monorepo root (only read when inMonorepo).
1200
+ * @param {boolean} options.inMonorepo - Whether the package lives in the monorepo.
1201
+ * @returns {string|null} The first piece of evidence, or null when fresh.
1202
+ */
1203
+ function distStaleReason(options) {
1204
+ const { srcDir, distDir, pkg, monorepoRoot, inMonorepo } = options;
1205
+
1206
+ if (!fs.existsSync(distDir)) {
1207
+ return 'dist/ is missing';
1208
+ }
1209
+
1210
+ const srcFiles = filesUnder(srcDir);
1211
+ const distFiles = filesUnder(distDir);
1212
+
1213
+ for (const [relative, mtime] of srcFiles) {
1214
+ const distMtime = distFiles.get(relative);
1215
+ if (distMtime === undefined) {
1216
+ return `dist/${relative} is missing`;
1217
+ }
1218
+ if (mtime > distMtime) {
1219
+ return `dist/${relative} is older than src/${relative}`;
1220
+ }
1221
+ }
1222
+
1223
+ const extras = allowedDistExtras(pkg);
1224
+ for (const relative of distFiles.keys()) {
1225
+ if (srcFiles.has(relative) || relative.startsWith(DIST_FIXTURE_STATE)) {
1226
+ continue;
1227
+ }
1228
+ if (!extras.some((extra) => relative === extra || relative.startsWith(`${extra}/`))) {
1229
+ return `dist/${relative} has no src counterpart (deleted source)`;
1230
+ }
1231
+ }
1232
+
1233
+ if (inMonorepo) {
1234
+ for (const name of FRESHNESS_VENDORABLES) {
1235
+ const vendorDir = path.join(distDir, 'vendor', name);
1236
+ if (fs.existsSync(vendorDir)
1237
+ && newestMtimeUnder(path.join(monorepoRoot, 'packages', name, 'src')) > newestMtimeUnder(vendorDir)) {
1238
+ return `dist/vendor/${name} is older than packages/${name}/src`;
1239
+ }
1240
+ }
1241
+
1242
+ // The declared assets the orphan scan above exempts: that exemption stopped
1243
+ // the false "deleted source" hits, but it left the destinations with NO
1244
+ // freshness question at all, so an edit to web's core/ or themes/ never made
1245
+ // desktop/extension stale and a watcher-down boot served the old copy
1246
+ // (#199). The source is a monorepo sibling package (the vendor hook copies
1247
+ // it verbatim, no build step), so the inMonorepo guard skips the loop on a
1248
+ // published install — where there is no sibling to compare against.
1249
+ for (const entry of (pkg && pkg.omega && pkg.omega.vendorAssets) || []) {
1250
+ if (!entry || !entry.package || !entry.from || !entry.to) {
1251
+ continue; // Malformed — the vendor hook is the one that fails on it
1252
+ }
1253
+ if (!entry.package.startsWith('@omega.js/')) {
1254
+ continue; // Foreign scope — the short-name → packages/<name> mapping below only holds for ours
1255
+ }
1256
+ const sourceMtime = newestMtimeOf(path.join(monorepoRoot, 'packages', entry.package.split('/').pop(), entry.from));
1257
+ if (sourceMtime === 0) {
1258
+ // A wrong `from` (rename/typo) lands here too and stays silent — the
1259
+ // vendor hook's warning owns that mistake; returning stale instead
1260
+ // would loop a full prepare on every boot with nothing to fix it
1261
+ continue;
1262
+ }
1263
+ const destination = path.join(distDir, entry.to);
1264
+ if (!fs.existsSync(destination)) {
1265
+ return `dist/${entry.to} was never vendored from ${entry.package}'s ${entry.from}`;
1266
+ }
1267
+ if (sourceMtime > newestMtimeOf(destination)) {
1268
+ return `dist/${entry.to} is older than ${entry.package}'s ${entry.from}`;
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ return null;
1274
+ }
1275
+
1276
+ /**
1277
+ * Whether a held heal lock can be stolen: its owner process is gone, or the
1278
+ * lock has no readable owner and is older than the mkdir→write grace (the
1279
+ * holder died mid-acquire).
1280
+ * @param {string} lockDir - The lock directory.
1281
+ * @returns {boolean}
1282
+ */
1283
+ function healLockAbandoned(lockDir) {
1284
+ let pid;
1285
+ try {
1286
+ pid = JSON.parse(fs.readFileSync(path.join(lockDir, 'owner.json'), 'utf8')).pid;
1287
+ } catch (e) {
1288
+ try {
1289
+ return Date.now() - fs.statSync(lockDir).mtimeMs > HEAL_LOCK_ORPHAN_MS;
1290
+ } catch (statError) {
1291
+ return false; // Lock vanished — the caller's next mkdir wins it
1292
+ }
1293
+ }
1294
+
1295
+ try {
1296
+ process.kill(pid, 0);
1297
+ return false;
1298
+ } catch (e) {
1299
+ // Only "no such process" means gone: EPERM is a LIVE process this user may
1300
+ // not signal, and stealing its lock would double the build it is running
1301
+ return e.code === 'ESRCH';
1302
+ }
1303
+ }
1304
+
1305
+ /**
1306
+ * Make the lock's parent (`<package>/.omega`) — the one piece of bookkeeping
1307
+ * that must exist before any mkdir-as-mutex attempt.
1308
+ * @param {string} lockDir - The lock directory.
1309
+ * @returns {boolean} False when the dir is unmakeable (read-only fs, full
1310
+ * disk) — the caller then proceeds unlocked rather than failing the heal.
1311
+ */
1312
+ function ensureHealLockParent(lockDir) {
1313
+ try {
1314
+ fs.mkdirSync(path.dirname(lockDir), { recursive: true });
1315
+ return true;
1316
+ } catch (e) {
1317
+ return false;
1318
+ }
1319
+ }
1320
+
1321
+ /**
1322
+ * ONE attempt at the mkdir-as-mutex, stealing an abandoned lock and retrying
1323
+ * once. Never throws: a heal must not fail over its own bookkeeping.
1324
+ * @param {string} lockDir - The lock directory.
1325
+ * @returns {boolean} Whether this process now holds the lock.
1326
+ */
1327
+ function tryHealLock(lockDir) {
1328
+ for (let attempt = 0; attempt < 2; attempt++) {
1329
+ try {
1330
+ fs.mkdirSync(lockDir);
1331
+ } catch (e) {
1332
+ if (attempt === 0 && healLockAbandoned(lockDir)) {
1333
+ fs.rmSync(lockDir, { recursive: true, force: true });
1334
+ continue;
1335
+ }
1336
+ return false;
1337
+ }
1338
+ try {
1339
+ fs.writeFileSync(path.join(lockDir, 'owner.json'), JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }));
1340
+ } catch (e) {
1341
+ // The owner file is liveness evidence, not the mutex itself — a lock
1342
+ // nobody can read is only abandoned after the orphan grace, which is
1343
+ // exactly the right answer for a holder that could not write it
1344
+ }
1345
+ return true;
1346
+ }
1347
+
1348
+ return false;
1349
+ }
1350
+
1351
+ /**
1352
+ * Release a held heal lock. Idempotent — a spawn that emits both 'error' and
1353
+ * 'exit' must never delete a lock a LATER process has since won.
1354
+ * @param {string} lockDir - The lock directory.
1355
+ * @returns {function} The release function.
1356
+ */
1357
+ function healLockReleaser(lockDir) {
1358
+ let released = false;
1359
+ return () => {
1360
+ if (released) {
1361
+ return;
1362
+ }
1363
+ released = true;
1364
+ fs.rmSync(lockDir, { recursive: true, force: true });
1365
+ };
1366
+ }
1367
+
1368
+ /**
1369
+ * Run fn under the package's cross-process heal lock, so two CLIs booting on
1370
+ * the same stale link produce ONE build. The loser waits for the winner (a
1371
+ * prepare can run for minutes), an abandoned lock is stolen, and a wait that
1372
+ * outlives the deadline proceeds unlocked rather than failing the boot.
1373
+ * @param {string} realDir - The package directory.
1374
+ * @param {function} fn - Critical section.
1375
+ * @returns {*} fn's result.
1376
+ */
1377
+ function withHealLock(realDir, fn) {
1378
+ const lockDir = path.join(realDir, HEAL_LOCK);
1379
+ const deadline = Date.now() + HEAL_LOCK_WAIT_MS;
1380
+ let release = null;
1381
+
1382
+ if (ensureHealLockParent(lockDir)) {
1383
+ while (true) {
1384
+ if (tryHealLock(lockDir)) {
1385
+ release = healLockReleaser(lockDir);
1386
+ break;
1387
+ }
1388
+ if (Date.now() >= deadline) {
1389
+ break;
1390
+ }
1391
+ sleepSync(HEAL_LOCK_POLL_MS);
1392
+ }
1393
+ }
1394
+
1395
+ try {
1396
+ return fn();
1397
+ } finally {
1398
+ if (release) {
1399
+ release();
1400
+ }
1401
+ }
1402
+ }
1403
+
1404
+ /**
1405
+ * The same heal lock, awaited WITHOUT blocking: the watch process takes it
1406
+ * around its own `npm run prepare` spawn, and must keep serving its other
1407
+ * watchers while a peer's prepare finishes (never sleepSync here).
1408
+ * @param {string} realDir - The package directory.
1409
+ * @returns {Promise<function>} Resolves to a release function — always safe to
1410
+ * call, including when the wait timed out and the caller proceeds unlocked.
1411
+ */
1412
+ function acquireHealLock(realDir) {
1413
+ const lockDir = path.join(realDir, HEAL_LOCK);
1414
+ const unlocked = () => {};
1415
+ if (!ensureHealLockParent(lockDir)) {
1416
+ return Promise.resolve(unlocked);
1417
+ }
1418
+
1419
+ const deadline = Date.now() + HEAL_LOCK_WAIT_MS;
1420
+ return new Promise((resolve) => {
1421
+ const attempt = () => {
1422
+ if (tryHealLock(lockDir)) {
1423
+ resolve(healLockReleaser(lockDir));
1424
+ return;
1425
+ }
1426
+ if (Date.now() >= deadline) {
1427
+ resolve(unlocked);
1428
+ return;
1429
+ }
1430
+ setTimeout(attempt, HEAL_LOCK_POLL_MS);
1431
+ };
1432
+ attempt();
1433
+ });
1434
+ }
1435
+
1436
+ /**
1437
+ * Resolve a package's REAL on-disk directory as seen from fromDir, mirroring
1438
+ * Node resolution. require.resolve of '<name>/package.json' first (works when
1439
+ * there is no exports map — @omega.js/backend), then a manual node_modules
1440
+ * walk-up (exports maps rarely expose './package.json', and a missing dist
1441
+ * makes the '.' entry unresolvable — exactly the stale case this exists for).
1442
+ * @param {string} packageName - Package name (e.g. '@omega.js/web').
1443
+ * @param {string} fromDir - Directory to resolve from.
1444
+ * @returns {string|null} Real package directory, or null when unresolvable.
1445
+ */
1446
+ function resolvePackageRealDir(packageName, fromDir) {
1447
+ try {
1448
+ const req = createRequire(path.join(fromDir, 'package.json'));
1449
+ return fs.realpathSync(path.dirname(req.resolve(`${packageName}/package.json`)));
1450
+ } catch (e) {
1451
+ // Fall through to the manual walk
1452
+ }
1453
+
1454
+ let dir = path.resolve(fromDir);
1455
+ while (true) {
1456
+ const candidate = path.join(dir, 'node_modules', packageName);
1457
+ if (fs.existsSync(path.join(candidate, 'package.json'))) {
1458
+ try {
1459
+ return fs.realpathSync(candidate);
1460
+ } catch (e) {
1461
+ return null;
1462
+ }
1463
+ }
1464
+ const parent = path.dirname(dir);
1465
+ if (parent === dir) {
1466
+ return null;
1467
+ }
1468
+ dir = parent;
1469
+ }
1470
+ }
1471
+
1472
+ /**
1473
+ * Whether a resolved directory is a SOURCE CHECKOUT rather than a registry
1474
+ * install: a real path still inside node_modules is installed, and an
1475
+ * unresolvable package is nothing at all.
1476
+ * @param {string|null} realDir - Resolved package directory.
1477
+ * @returns {boolean}
1478
+ */
1479
+ function isLocalCheckout(realDir) {
1480
+ return Boolean(realDir) && !realDir.split(path.sep).includes('node_modules');
1481
+ }
1482
+
1483
+ /**
1484
+ * Read a package's manifest.
1485
+ * @param {string} realDir - The package directory.
1486
+ * @returns {object|null} The manifest, or null when it is unreadable.
1487
+ */
1488
+ function readPackageManifest(realDir) {
1489
+ try {
1490
+ return JSON.parse(fs.readFileSync(path.join(realDir, 'package.json'), 'utf8'));
1491
+ } catch (e) {
1492
+ return null;
1493
+ }
1494
+ }
1495
+
1496
+ // One watch-down warning per process, however many packages get checked.
1497
+ let warnedWatchDown = false;
1498
+
1499
+ /**
1500
+ * Warn once that the monorepo's src→dist watch is not running: every framework
1501
+ * CLI boots through the freshness path, so all five surfaces get this for free.
1502
+ * The boot's own heal (#398) does not retire it — it is a boot-time repair of a
1503
+ * STALE dist, and this warning fires on a fresh one too, so the wording states
1504
+ * the standing deal rather than claiming a heal this boot may not have run.
1505
+ * @param {string} monorepoRoot - Monorepo root path.
1506
+ */
1507
+ function warnWatchDown(monorepoRoot) {
1508
+ if (warnedWatchDown) {
1509
+ return;
1510
+ }
1511
+ warnedWatchDown = true;
1512
+ console.warn(`omega: the monorepo src→dist watch is not running: a stale linked dist is healed once at boot, but nothing rebuilds it as you edit — run \`npm start\` in ${monorepoRoot} for live rebuilds`);
1513
+ }
1514
+
1515
+ /**
1516
+ * The loud stop for a monorepo-linked package whose dist is missing or stale
1517
+ * while its watch RUNS (#281) — the only way a 'stale-linked' result happens
1518
+ * (#398). A consumer build does not fix it: that watch owns the build and is
1519
+ * mid-flight, so the build says what is unbuilt, where, and who is building it.
1520
+ * @param {object} result - A 'stale-linked' ensureFreshLocalDist result.
1521
+ * @returns {string} The multi-line message, stderr-bound.
1522
+ */
1523
+ function staleLinkedMessage(result) {
1524
+ return [
1525
+ '',
1526
+ `omega: ${result.packageName} is linked into the omega monorepo and its dist is not built (${result.reason}).`,
1527
+ `omega: linked packages are read-only to consumer builds, so this build will not rebuild ${result.dir}.`,
1528
+ `omega: the monorepo watch (\`npm start\` in ${result.monorepoRoot}) is running but has not landed that build yet: give it a moment, then re-run this command.`,
1529
+ '',
1530
+ ].join('\n');
1531
+ }
1532
+
1533
+ /**
1534
+ * The loud stop's sibling for a monorepo-linked package whose watch-down heal
1535
+ * FAILED (#398). That heal was the dist's only chance — no watch is coming to
1536
+ * land the build — so continuing would serve code nobody built, the silent
1537
+ * fallback #281 forbids. Say which prepare broke and where to run it by hand.
1538
+ * @param {object} result - A 'heal-failed' ensureFreshLocalDist result.
1539
+ * @returns {string} The multi-line message, stderr-bound.
1540
+ */
1541
+ function healFailedMessage(result) {
1542
+ return [
1543
+ '',
1544
+ `omega: ${result.packageName} is linked into the omega monorepo with a stale dist (${result.reason}), and this boot's rebuild FAILED (${result.failure}).`,
1545
+ `omega: the monorepo watch is not running, so nothing else will build it — running on that dist would serve code nobody built.`,
1546
+ `omega: run \`npm run prepare\` in ${result.dir} by hand, fix what it reports, then re-run this command (or start the watch: \`npm start\` in ${result.monorepoRoot}).`,
1547
+ '',
1548
+ ].join('\n');
1549
+ }
1550
+
1551
+ /**
1552
+ * Check that a locally-linked framework's dist is at least as new as its src —
1553
+ * reporting or rebuilding it when a src edit landed without a prepare, so
1554
+ * consumers never run stale dist code just because nobody remembered to
1555
+ * rebuild (Ian's ask, 2026-07-20).
1556
+ *
1557
+ * Only acts on SOURCE CHECKOUTS: a registry install (real path still inside
1558
+ * node_modules) is untouched. Staleness is PER-FILE evidence (distStaleReason),
1559
+ * never a whole-tree mtime compare. A live monorepo watch buys a bounded grace
1560
+ * for its in-flight copy — then the check reports anyway, because a watcher that
1561
+ * silently died is exactly what let a stale dist serve for a day (#195).
1562
+ *
1563
+ * A link into the OMEGA MONOREPO is read-only here WHILE ITS WATCH RUNS (#281,
1564
+ * narrowed by #398): that checkout is shared (a fleet of agents, several brands,
1565
+ * the monorepo's own processes), so a consumer build never prepares out from
1566
+ * under the process that owns the build — a prepare purges the dist a sibling is
1567
+ * mid-require on, and refetches network caches, all of it invisible to git.
1568
+ * Those come back 'stale-linked' and freshnessBoot stops the invocation loudly.
1569
+ * With the watch DOWN nothing else owns that rebuild, so it heals like any other
1570
+ * checkout — the heal lock still serializes the CLIs booting on it, and the
1571
+ * largest concurrent writer is by definition not running. Every heal takes the
1572
+ * package's cross-process lock, so N booting CLIs produce one build. When THAT
1573
+ * heal fails it comes back 'heal-failed', another loud stop: no watch is coming
1574
+ * to land the build, so there is no stale dist to fall back onto. A plain
1575
+ * checkout's failed rebuild stays 'rebuild-failed' and continues — its dist is
1576
+ * the developer's own to fix, and nothing shared depends on this boot stopping.
1577
+ * @param {object} options
1578
+ * @param {string} options.packageName - The package to check (e.g. '@omega.js/web').
1579
+ * @param {string} [options.fromDir] - Resolution origin (default process.cwd()).
1580
+ * @returns {{status: 'skipped'|'reexec-guard'|'registry'|'not-buildable'|'fresh'|'rebuilt'|'stale-linked'|'rebuild-failed'|'heal-failed', by?: 'self'|'watch'|'peer', packageName: string, dir?: string, reason?: string, failure?: string, monorepoRoot?: string}}
1581
+ * Every HEALED outcome is 'rebuilt' — `by` only says who built it — because
1582
+ * whoever built it, this process booted from the pre-heal dist and must
1583
+ * re-exec.
1584
+ */
1585
+ function ensureFreshLocalDist(options) {
1586
+ const { packageName, fromDir = process.cwd() } = options;
1587
+
1588
+ // Env seams: test/CI hatch, and the loop guard freshnessBoot sets on re-exec
1589
+ if (process.env.OMEGA_SKIP_FRESHNESS) {
1590
+ return { status: 'skipped', packageName };
1591
+ }
1592
+ if (process.env.OMEGA_FRESH_REEXEC) {
1593
+ return { status: 'reexec-guard', packageName };
1594
+ }
1595
+
1596
+ const realDir = resolvePackageRealDir(packageName, fromDir);
1597
+ if (!isLocalCheckout(realDir)) {
1598
+ // Unresolvable (bootstrap dir, nothing installed) or a real registry
1599
+ // install — either way there is no local source checkout to freshen
1600
+ return { status: 'registry', packageName, dir: realDir || undefined };
1601
+ }
1602
+
1603
+ const pkg = readPackageManifest(realDir);
1604
+ const srcDir = path.join(realDir, 'src');
1605
+ if (!fs.existsSync(srcDir) || !(pkg && pkg.scripts && pkg.scripts.prepare)) {
1606
+ return { status: 'not-buildable', packageName, dir: realDir };
1607
+ }
1608
+
1609
+ const distDir = path.join(realDir, 'dist');
1610
+ const monorepoRoot = path.dirname(path.dirname(realDir));
1611
+ const inMonorepo = isMonorepoRoot(monorepoRoot);
1612
+ const watchPid = inMonorepo ? readLiveWatchPid(monorepoRoot) : null;
1613
+
1614
+ // Liveness: a monorepo-linked CLI booting with no watch running still heals
1615
+ // itself below, but src→dist propagation is only as live as this boot — say
1616
+ // so once, loudly, instead of letting a dead watcher go unnoticed for a day
1617
+ if (inMonorepo && !watchPid) {
1618
+ warnWatchDown(monorepoRoot);
1619
+ }
1620
+
1621
+ const staleness = () => distStaleReason({ srcDir, distDir, pkg, monorepoRoot, inMonorepo });
1622
+ let reason = staleness();
1623
+ if (!reason) {
1624
+ return { status: 'fresh', packageName, dir: realDir };
1625
+ }
1626
+
1627
+ // A live watch owns the copy — give its in-flight write a bounded moment to
1628
+ // land, then heal anyway (blind trust of the lock is what masked #195)
1629
+ if (watchPid) {
1630
+ const deadline = Date.now() + WATCH_GRACE_MS;
1631
+ while (Date.now() < deadline) {
1632
+ sleepSync(WATCH_POLL_MS);
1633
+ reason = staleness();
1634
+ if (!reason) {
1635
+ console.log(`\x1b[2momega: local ${packageName} dist was stale — the monorepo watch rebuilt it\x1b[0m`);
1636
+ return { status: 'rebuilt', by: 'watch', packageName, dir: realDir };
1637
+ }
1638
+ }
1639
+ }
1640
+
1641
+ // Linked into the monorepo WITH its watch running: that watch owns this dist
1642
+ // and will land the build itself, so a consumer build stays out of it (#281).
1643
+ // Report it and let the boot stop the invocation. Watch DOWN falls through to
1644
+ // the heal below — nothing else owns the rebuild then (#398).
1645
+ if (inMonorepo && watchPid) {
1646
+ return { status: 'stale-linked', packageName, dir: realDir, reason, monorepoRoot };
1647
+ }
1648
+
1649
+ return withHealLock(realDir, () => {
1650
+ // The lock loser arrives after the winner's prepare: re-check instead of
1651
+ // building again — running twice equals running once. Still a HEAL, so it
1652
+ // re-execs like any other: this process's modules came from the stale dist
1653
+ reason = staleness();
1654
+ if (!reason) {
1655
+ return { status: 'rebuilt', by: 'peer', packageName, dir: realDir };
1656
+ }
1657
+
1658
+ console.log(`omega: local ${packageName} dist is stale (${reason}) — rebuilding…`);
1659
+ const result = spawnSync('npm', ['run', 'prepare'], {
1660
+ cwd: realDir,
1661
+ stdio: 'inherit',
1662
+ shell: process.platform === 'win32',
1663
+ });
1664
+ if (result.status !== 0) {
1665
+ const failure = `npm run prepare exited ${result.status === null ? String(result.error && result.error.message || 'spawn error') : result.status}`;
1666
+ if (inMonorepo) {
1667
+ // The watch is down (a live one never reaches this heal), so this
1668
+ // prepare was the dist's only chance — the boot stops on it (#398)
1669
+ return { status: 'heal-failed', packageName, dir: realDir, reason, failure, monorepoRoot };
1670
+ }
1671
+ console.warn(`omega: rebuild failed (${failure} in ${realDir}) — continuing on the stale dist`);
1672
+ return { status: 'rebuild-failed', packageName, dir: realDir };
1673
+ }
1674
+ return { status: 'rebuilt', by: 'self', packageName, dir: realDir };
1675
+ });
1676
+ }
1677
+
1678
+ /**
1679
+ * The ordered list of packages ONE boot checks: the CLI host plus every
1680
+ * `@omega.js/*` RUNTIME dependency reachable from it through local checkouts.
1681
+ *
1682
+ * The host alone is not enough: web's bundle carries `@omega.js/client`'s dist
1683
+ * verbatim, so a boot that heals web and stops there still serves the browser a
1684
+ * stale client (#198). devDependencies are never walked — nothing a consumer
1685
+ * RUNS comes from them. A dep that resolves into node_modules (registry
1686
+ * install) or nowhere stays on the list — ensureFreshLocalDist classifies it —
1687
+ * but its own deps do not, because there is no local source under it to go
1688
+ * stale. Each dep resolves from ITS depender's real dir, so the walk follows
1689
+ * the actual node_modules chain rather than guessing a layout.
1690
+ *
1691
+ * Order is post-order — DEPS FIRST, host LAST — so a host prepare that consumes
1692
+ * a dep's artifacts (vendored copies, bundled dists) sees the freshened ones.
1693
+ * @param {object} options
1694
+ * @param {string} options.packageName - The CLI host package.
1695
+ * @param {string} [options.fromDir] - Resolution origin (default process.cwd()).
1696
+ * @returns {Array<{packageName: string, fromDir: string}>} ensureFreshLocalDist arguments, in check order.
1697
+ */
1698
+ function freshnessCheckList(options) {
1699
+ const { packageName, fromDir = process.cwd() } = options;
1700
+ const list = [];
1701
+ const visited = new Set();
1702
+
1703
+ const walk = (name, origin) => {
1704
+ if (visited.has(name)) {
1705
+ return; // A cycle or a diamond — one check per package either way
1706
+ }
1707
+ visited.add(name);
1708
+
1709
+ const realDir = resolvePackageRealDir(name, origin);
1710
+ if (isLocalCheckout(realDir)) {
1711
+ const dependencies = (readPackageManifest(realDir) || {}).dependencies || {};
1712
+ for (const dep of Object.keys(dependencies)) {
1713
+ if (dep.startsWith(SCOPE)) {
1714
+ walk(dep, realDir);
1715
+ }
1716
+ }
1717
+ }
1718
+
1719
+ list.push({ packageName: name, fromDir: origin });
1720
+ };
1721
+
1722
+ walk(packageName, fromDir);
1723
+ return list;
1724
+ }
1725
+
1726
+ // One freshness scan per process per module instance — a dispatcher hop that
1727
+ // re-enters the SAME framework's run() must not scan (or rebuild) twice.
1728
+ let freshnessBootRan = false;
1729
+
1730
+ /**
1731
+ * CLI-boot wiring for ensureFreshLocalDist: every framework run() calls this
1732
+ * first, naming the HOST package — and the boot checks the host's whole
1733
+ * `@omega.js/*` runtime dependency closure (freshnessCheckList), deps first,
1734
+ * because the host's dist is not the only code this invocation serves (#198).
1735
+ * On a rebuild ANYWHERE in that list, the running process booted from the STALE
1736
+ * dist — so the same invocation re-execs ONCE (OMEGA_FRESH_REEXEC guards the
1737
+ * loop) and this process exits with the child's status. That is EVERY heal,
1738
+ * whoever built it (`by: self|watch|peer`): a dist the watch or a peer CLI
1739
+ * rebuilt leaves this process just as stale as one it rebuilt itself.
1740
+ *
1741
+ * A 'stale-linked' entry is the LOUD STOP (#281): the package lives in the
1742
+ * shared monorepo with its watch mid-build, nothing here may build over it, and
1743
+ * running on a dist nobody built is the silent fallback the ruling forbids — so
1744
+ * the boot prints what is unbuilt and exits 1 before the verb runs. With that
1745
+ * watch down the entry heals instead (#398) and re-execs like any other — and a
1746
+ * heal that FAILS ('heal-failed') is the same loud stop for the same reason:
1747
+ * with no watch coming, the stale dist is not something to fall back onto. Every
1748
+ * other outcome returns the HOST's result and the boot continues.
1749
+ * @param {object} options - Same as ensureFreshLocalDist (packageName = the host).
1750
+ * @returns {{status: string, by?: string, packageName: string, dir?: string}}
1751
+ */
1752
+ function freshnessBoot(options) {
1753
+ if (freshnessBootRan) {
1754
+ return { status: 'already-checked', packageName: options.packageName };
1755
+ }
1756
+ freshnessBootRan = true;
1757
+
1758
+ // The whole list runs even after a heal: a dep rebuild can leave its
1759
+ // dependents stale in turn, and the re-exec below covers them all at once
1760
+ let result = null;
1761
+ let healed = false;
1762
+ for (const entry of freshnessCheckList(options)) {
1763
+ const entryResult = ensureFreshLocalDist(entry);
1764
+ if (entryResult.status === 'stale-linked') {
1765
+ console.error(staleLinkedMessage(entryResult));
1766
+ process.exit(1);
1767
+ }
1768
+ if (entryResult.status === 'heal-failed') {
1769
+ console.error(healFailedMessage(entryResult));
1770
+ process.exit(1);
1771
+ }
1772
+ healed = healed || entryResult.status === 'rebuilt';
1773
+ if (entry.packageName === options.packageName) {
1774
+ result = entryResult; // The host is last — its result is the boot's
1775
+ }
1776
+ }
1777
+
1778
+ if (!healed) {
1779
+ return result;
1780
+ }
1781
+
1782
+ const child = spawnSync(process.execPath, process.argv.slice(1), {
1783
+ stdio: 'inherit',
1784
+ env: Object.assign({}, process.env, { OMEGA_FRESH_REEXEC: '1' }),
1785
+ });
1786
+ process.exit(child.status === null ? 1 : child.status);
1787
+ }
1788
+
1789
+ /**
1790
+ * ONE freshness pass for a FAN-OUT, run BEFORE it (#340): the brand-root
1791
+ * `omega dev` sweeps every lane's host closure in this process, then spawns the
1792
+ * lanes — each lane's own freshnessBoot then finds nothing to do.
1793
+ *
1794
+ * Why it must be hoisted: a heal runs `npm run prepare`, which PURGES dist
1795
+ * before recopying, and a lane's framework bin opens by loading its own built
1796
+ * dist entry. Two lanes checking themselves in parallel means one lane's purge
1797
+ * window is the other lane's require — the backend lane died MODULE_NOT_FOUND
1798
+ * dispatching through the web package the web lane was mid-rebuild on. The heal lock does
1799
+ * not help: it serializes BUILDS, and the casualty is a bystander require.
1800
+ *
1801
+ * The lanes' closures overlap almost entirely (they share the brand's
1802
+ * node_modules), so entries are deduped by the package DIRECTORY they resolve
1803
+ * to — one check per real package, deps-first per host (freshnessCheckList).
1804
+ *
1805
+ * Nothing here re-execs, unlike freshnessBoot: what this pass heals is the
1806
+ * LANES' code, which no lane has loaded yet — this process's own closure was
1807
+ * settled by its CLI-boot freshnessBoot. That is also why the re-exec loop
1808
+ * guard is lifted for the pass: a dev boot that re-execed after healing its own
1809
+ * host must still hoist the lanes' check, or the fan-out races exactly as before.
1810
+ *
1811
+ * A 'stale-linked' entry (a monorepo link whose watch is mid-build, read-only
1812
+ * here — #281) is REPORTED, message and all, and returned: the caller stops the
1813
+ * boot before any lane spawns, instead of each lane discovering it separately,
1814
+ * half-booted. A 'heal-failed' entry (a monorepo link whose watch-down heal
1815
+ * broke — #398) comes back the same way, for the same reason.
1816
+ * @param {object} options
1817
+ * @param {Array<{packageName: string, fromDir: string}>} options.hosts - One entry
1818
+ * per lane: the framework package that lane runs, and the target dir it resolves from.
1819
+ * @returns {{checked: object[], healed: string[], staleLinked: object[], healFailed: object[], failed: string[]}}
1820
+ * checked = every ensureFreshLocalDist result, in check order; staleLinked and
1821
+ * healFailed are the caller's two stop conditions, failed is the plain
1822
+ * checkouts that warned and carried on.
1823
+ */
1824
+ function freshnessSweep(options) {
1825
+ const { hosts = [] } = options;
1826
+
1827
+ const entries = [];
1828
+ const seen = new Set();
1829
+ for (const host of hosts) {
1830
+ for (const entry of freshnessCheckList(host)) {
1831
+ // The resolved dir is the identity — the same package reached from two
1832
+ // targets is ONE dist. Unresolvable entries keep their own key so
1833
+ // ensureFreshLocalDist still gets to classify them.
1834
+ const key = resolvePackageRealDir(entry.packageName, entry.fromDir) || `${entry.packageName}\u0000${entry.fromDir}`;
1835
+ if (seen.has(key)) {
1836
+ continue;
1837
+ }
1838
+ seen.add(key);
1839
+ entries.push(entry);
1840
+ }
1841
+ }
1842
+
1843
+ const priorReexec = process.env.OMEGA_FRESH_REEXEC;
1844
+ delete process.env.OMEGA_FRESH_REEXEC;
1845
+
1846
+ const checked = [];
1847
+ try {
1848
+ for (const entry of entries) {
1849
+ checked.push(ensureFreshLocalDist(entry));
1850
+ }
1851
+ } finally {
1852
+ if (priorReexec !== undefined) {
1853
+ process.env.OMEGA_FRESH_REEXEC = priorReexec;
1854
+ }
1855
+ }
1856
+
1857
+ const staleLinked = checked.filter((result) => result.status === 'stale-linked');
1858
+ for (const result of staleLinked) {
1859
+ console.error(staleLinkedMessage(result));
1860
+ }
1861
+
1862
+ const healFailed = checked.filter((result) => result.status === 'heal-failed');
1863
+ for (const result of healFailed) {
1864
+ console.error(healFailedMessage(result));
1865
+ }
1866
+
1867
+ return {
1868
+ checked,
1869
+ healed: checked.filter((result) => result.status === 'rebuilt').map((result) => result.packageName),
1870
+ staleLinked,
1871
+ healFailed,
1872
+ failed: checked.filter((result) => result.status === 'rebuild-failed').map((result) => result.packageName),
1873
+ };
1874
+ }
1875
+
1876
+ // Exports
1877
+ module.exports = {
1878
+ DEFAULT_MONOREPO,
1879
+ WATCH_LOCK,
1880
+ WATCH_LOG,
1881
+ VENDOR_PROPAGATION_LOCK,
1882
+ HEAL_LOCK,
1883
+ isMonorepoRoot,
1884
+ resolveMonorepoRoot,
1885
+ packageDir,
1886
+ findBrandRoot,
1887
+ discoverTargets,
1888
+ frameworkPackagesOf,
1889
+ linkLocalPackages,
1890
+ restoreRegistrySpecs,
1891
+ resolveLinkedMonorepo,
1892
+ createWatchReadyTracker,
1893
+ awaitRunningWatchReady,
1894
+ startMonorepoWatch,
1895
+ startVendorPropagation,
1896
+ vendorPropagationActive,
1897
+ readLiveWatchPid,
1898
+ acquireWatchLock,
1899
+ releaseWatchLock,
1900
+ FRESHNESS_VENDORABLES,
1901
+ ensureFreshLocalDist,
1902
+ freshnessCheckList,
1903
+ freshnessBoot,
1904
+ freshnessSweep,
1905
+ };