@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.
- package/LICENSE +98 -0
- package/README.md +640 -0
- package/bin/omega +2 -0
- package/bin/omega-web +2 -0
- package/bin/omg +2 -0
- package/core/_includes/admin/sections/sidebar.json +100 -0
- package/core/_includes/admin/sections/topbar.json +11 -0
- package/core/_includes/backend/sections/sidebar.json +39 -0
- package/core/_includes/backend/sections/topbar.json +16 -0
- package/core/_includes/core/body.html +102 -0
- package/core/_includes/core/foot.html +536 -0
- package/core/_includes/core/head.html +423 -0
- package/core/_includes/core/pricing/price-per-unit.html +33 -0
- package/core/_includes/core/showcase/entry-body.html +94 -0
- package/core/_includes/core/showcase/index-body.html +43 -0
- package/core/_includes/core/showcase/nav.html +60 -0
- package/core/_includes/frontend/sections/footer.json +74 -0
- package/core/_includes/frontend/sections/nav.json +70 -0
- package/core/_includes/global/sections/account.json +31 -0
- package/core/_includes/modules/engagement/giscus.html +26 -0
- package/core/_layouts/blueprint/404.html +10 -0
- package/core/_layouts/blueprint/about.html +10 -0
- package/core/_layouts/blueprint/account/index.html +11 -0
- package/core/_layouts/blueprint/admin/calendar/index.html +590 -0
- package/core/_layouts/blueprint/admin/dashboard/index.html +301 -0
- package/core/_layouts/blueprint/admin/firebase/index.html +214 -0
- package/core/_layouts/blueprint/admin/posts/editor.html +157 -0
- package/core/_layouts/blueprint/admin/posts/index.html +126 -0
- package/core/_layouts/blueprint/admin/stackblitz/index.html +30 -0
- package/core/_layouts/blueprint/admin/studio/index.html +85 -0
- package/core/_layouts/blueprint/admin/users/index.html +272 -0
- package/core/_layouts/blueprint/admin/users/new.html +82 -0
- package/core/_layouts/blueprint/admin/verts/index.html +204 -0
- package/core/_layouts/blueprint/alternatives/alternative.html +15 -0
- package/core/_layouts/blueprint/alternatives/index.html +10 -0
- package/core/_layouts/blueprint/app.html +10 -0
- package/core/_layouts/blueprint/auth/reset.html +19 -0
- package/core/_layouts/blueprint/auth/signin.html +19 -0
- package/core/_layouts/blueprint/auth/signup.html +19 -0
- package/core/_layouts/blueprint/auth/token.html +19 -0
- package/core/_layouts/blueprint/blog/categories/category.html +8 -0
- package/core/_layouts/blueprint/blog/categories/index.html +11 -0
- package/core/_layouts/blueprint/blog/index.html +10 -0
- package/core/_layouts/blueprint/blog/post.html +5 -0
- package/core/_layouts/blueprint/blog/tags/index.html +11 -0
- package/core/_layouts/blueprint/blog/tags/tag.html +8 -0
- package/core/_layouts/blueprint/careers.md +13 -0
- package/core/_layouts/blueprint/collection/category.html +9 -0
- package/core/_layouts/blueprint/collection/document.html +9 -0
- package/core/_layouts/blueprint/collection/index.html +9 -0
- package/core/_layouts/blueprint/connections/callback.html +11 -0
- package/core/_layouts/blueprint/contact.html +14 -0
- package/core/_layouts/blueprint/dashboard/index.html +10 -0
- package/core/_layouts/blueprint/download.html +20 -0
- package/core/_layouts/blueprint/extension/index.html +14 -0
- package/core/_layouts/blueprint/extension/installed.html +11 -0
- package/core/_layouts/blueprint/feedback.html +10 -0
- package/core/_layouts/blueprint/index.html +16 -0
- package/core/_layouts/blueprint/legal/cookies.md +57 -0
- package/core/_layouts/blueprint/legal/privacy.md +109 -0
- package/core/_layouts/blueprint/legal/terms.md +155 -0
- package/core/_layouts/blueprint/payment/checkout.html +15 -0
- package/core/_layouts/blueprint/payment/confirmation.html +14 -0
- package/core/_layouts/blueprint/portal/email-preferences.html +15 -0
- package/core/_layouts/blueprint/pricing.html +20 -0
- package/core/_layouts/blueprint/status.html +10 -0
- package/core/_layouts/blueprint/team/index.html +10 -0
- package/core/_layouts/blueprint/team/member.html +5 -0
- package/core/_layouts/blueprint/updates/index.html +10 -0
- package/core/_layouts/blueprint/updates/update.html +10 -0
- package/core/_layouts/core/root.html +45 -0
- package/core/_layouts/modules/utilities/redirect.html +129 -0
- package/core/css/_studio.scss +237 -0
- package/core/css/components/_index.scss +146 -0
- package/core/css/core/_.scss +0 -0
- package/core/css/core/_alert.scss +87 -0
- package/core/css/core/_animations.scss +343 -0
- package/core/css/core/_bindings.scss +116 -0
- package/core/css/core/_blog-search.scss +57 -0
- package/core/css/core/_chatsy.scss +61 -0
- package/core/css/core/_consent.scss +424 -0
- package/core/css/core/_custom-font-awesome.scss +146 -0
- package/core/css/core/_exit-popup.scss +42 -0
- package/core/css/core/_generated-badge.scss +33 -0
- package/core/css/core/_initialize.scss +29 -0
- package/core/css/core/_lazy-loading.scss +104 -0
- package/core/css/core/_social-sharing.scss +41 -0
- package/core/css/core/_tones.scss +32 -0
- package/core/css/core/_utilities.scss +157 -0
- package/core/css/main.scss +54 -0
- package/core/css/motion/_index.scss +368 -0
- package/core/css/pages/404/index.scss +0 -0
- package/core/css/pages/admin/calendar/index.scss +692 -0
- package/core/css/pages/admin/firebase/index.scss +33 -0
- package/core/css/pages/admin/index.scss +9 -0
- package/core/css/pages/admin/users/index.scss +9 -0
- package/core/css/pages/admin/verts/index.scss +9 -0
- package/core/css/pages/alternatives/[alternative]/index.scss +0 -0
- package/core/css/pages/blog/[slug].scss +55 -0
- package/core/css/pages/dashboard/account/index.scss +242 -0
- package/core/css/pages/download/index.scss +480 -0
- package/core/css/pages/extension/installed/index.scss +317 -0
- package/core/css/pages/feedback/index.scss +31 -0
- package/core/css/pages/payment/confirmation/index.scss +14 -0
- package/core/css/pages/pricing/index.scss +0 -0
- package/core/css/pages/status/index.scss +162 -0
- package/core/css/pages/test/libraries/layers/index.scss +28 -0
- package/core/css/pages/updates/[update].scss +46 -0
- package/core/css/pages/updates/index.scss +13 -0
- package/core/css/shell/_index.scss +207 -0
- package/core/css/tokens/_index.scss +166 -0
- package/core/icons/flags/ar.svg +1 -0
- package/core/icons/flags/bd.svg +1 -0
- package/core/icons/flags/ca.svg +1 -0
- package/core/icons/flags/ch.svg +1 -0
- package/core/icons/flags/cn.svg +1 -0
- package/core/icons/flags/co.svg +1 -0
- package/core/icons/flags/cw.svg +1 -0
- package/core/icons/flags/cz.svg +1 -0
- package/core/icons/flags/de.svg +1 -0
- package/core/icons/flags/dk.svg +1 -0
- package/core/icons/flags/eg.svg +1 -0
- package/core/icons/flags/es.svg +1 -0
- package/core/icons/flags/fi.svg +1 -0
- package/core/icons/flags/fr.svg +1 -0
- package/core/icons/flags/gb.svg +1 -0
- package/core/icons/flags/gq.svg +1 -0
- package/core/icons/flags/gr.svg +1 -0
- package/core/icons/flags/gt.svg +1 -0
- package/core/icons/flags/hn.svg +1 -0
- package/core/icons/flags/id.svg +1 -0
- package/core/icons/flags/ie.svg +1 -0
- package/core/icons/flags/in.svg +1 -0
- package/core/icons/flags/ir.svg +1 -0
- package/core/icons/flags/it.svg +1 -0
- package/core/icons/flags/jp.svg +1 -0
- package/core/icons/flags/kp.svg +1 -0
- package/core/icons/flags/kr.svg +1 -0
- package/core/icons/flags/ly.svg +1 -0
- package/core/icons/flags/ma.svg +1 -0
- package/core/icons/flags/mu.svg +1 -0
- package/core/icons/flags/mx.svg +1 -0
- package/core/icons/flags/ng.svg +1 -0
- package/core/icons/flags/pa.svg +1 -0
- package/core/icons/flags/ph.svg +1 -0
- package/core/icons/flags/pk.svg +1 -0
- package/core/icons/flags/pt.svg +1 -0
- package/core/icons/flags/ru.svg +1 -0
- package/core/icons/flags/sa.svg +1 -0
- package/core/icons/flags/sd.svg +1 -0
- package/core/icons/flags/se.svg +1 -0
- package/core/icons/flags/th.svg +1 -0
- package/core/icons/flags/tm.svg +1 -0
- package/core/icons/flags/tr.svg +1 -0
- package/core/icons/flags/us.svg +1 -0
- package/core/icons/flags/uy.svg +1 -0
- package/core/icons/flags/vn.svg +1 -0
- package/core/icons/flags/za.svg +1 -0
- package/core/images/exit-popup/subscriber-1.jpg +0 -0
- package/core/images/exit-popup/subscriber-2.jpg +0 -0
- package/core/images/exit-popup/subscriber-3.jpg +0 -0
- package/core/images/exit-popup/subscriber-4.jpg +0 -0
- package/core/images/placeholder/photo-1.jpg +0 -0
- package/core/images/placeholder/photo-2.jpg +0 -0
- package/core/images/placeholder/photo-3.jpg +0 -0
- package/core/js/core/analytics-loader.js +253 -0
- package/core/js/core/app-shell.js +136 -0
- package/core/js/core/appearance.js +195 -0
- package/core/js/core/auth.js +457 -0
- package/core/js/core/complete.js +72 -0
- package/core/js/core/consent.js +384 -0
- package/core/js/core/dev-icon-audit.js +63 -0
- package/core/js/core/dev-palette.js +670 -0
- package/core/js/core/dev-sections.js +53 -0
- package/core/js/core/exit-popup.js +276 -0
- package/core/js/core/initialize.js +8 -0
- package/core/js/core/language-switcher.js +167 -0
- package/core/js/core/lazy-loading.js +479 -0
- package/core/js/core/motion.js +28 -0
- package/core/js/core/query-strings.js +146 -0
- package/core/js/core/service-worker.js +28 -0
- package/core/js/core/social-sharing.js +293 -0
- package/core/js/first-paint.js +35 -0
- package/core/js/layouts/modules/utilities/redirect.js +122 -0
- package/core/js/libs/admin-helpers.js +62 -0
- package/core/js/libs/alert-dismiss.js +35 -0
- package/core/js/libs/analytics.js +368 -0
- package/core/js/libs/auth/email.js +144 -0
- package/core/js/libs/auth/errors.js +119 -0
- package/core/js/libs/auth/forms.js +206 -0
- package/core/js/libs/auth/index.js +181 -0
- package/core/js/libs/auth/oauth.js +394 -0
- package/core/js/libs/auth/orphan.js +110 -0
- package/core/js/libs/auth/password-toggle.js +44 -0
- package/core/js/libs/auth/session-params.js +266 -0
- package/core/js/libs/auth/tracking.js +41 -0
- package/core/js/libs/charts.js +516 -0
- package/core/js/libs/consent-region.js +127 -0
- package/core/js/libs/dev.js +150 -0
- package/core/js/libs/graph.js +201 -0
- package/core/js/libs/initialize-tooltips.js +31 -0
- package/core/js/libs/logger.js +38 -0
- package/core/js/libs/omega-copy.js +89 -0
- package/core/js/libs/path-prefix.js +44 -0
- package/core/js/libs/payment-config.js +76 -0
- package/core/js/libs/recaptcha.js +94 -0
- package/core/js/libs/sale-name.js +295 -0
- package/core/js/libs/showcase-frames.js +37 -0
- package/core/js/libs/srcset.js +69 -0
- package/core/js/libs/tracking-consent.js +161 -0
- package/core/js/main.js +144 -0
- package/core/js/pages/404/index.js +57 -0
- package/core/js/pages/admin/calendar/calendar-core.js +701 -0
- package/core/js/pages/admin/calendar/calendar-events.js +853 -0
- package/core/js/pages/admin/calendar/calendar-renderer.js +807 -0
- package/core/js/pages/admin/calendar/campaign-preview.js +105 -0
- package/core/js/pages/admin/calendar/index.js +40 -0
- package/core/js/pages/admin/firebase/index.js +727 -0
- package/core/js/pages/admin/index.js +531 -0
- package/core/js/pages/admin/posts/editor/index.js +230 -0
- package/core/js/pages/admin/posts/index.js +237 -0
- package/core/js/pages/admin/studio/index.js +538 -0
- package/core/js/pages/admin/users/index.js +669 -0
- package/core/js/pages/admin/users/new/index.js +65 -0
- package/core/js/pages/admin/verts/index.js +407 -0
- package/core/js/pages/alternatives/[alternative]/index.js +17 -0
- package/core/js/pages/app/index.js +135 -0
- package/core/js/pages/blog/[slug].js +132 -0
- package/core/js/pages/blog/_search.mjs +108 -0
- package/core/js/pages/blog/index.js +193 -0
- package/core/js/pages/blog/page/[num]/index.js +7 -0
- package/core/js/pages/connections/callback/index.js +148 -0
- package/core/js/pages/contact/index.js +170 -0
- package/core/js/pages/cookies.js +2 -0
- package/core/js/pages/dashboard/account/index.js +366 -0
- package/core/js/pages/dashboard/account/modules/orders.js +82 -0
- package/core/js/pages/dashboard/account/sections/api-keys.js +149 -0
- package/core/js/pages/dashboard/account/sections/billing.js +1667 -0
- package/core/js/pages/dashboard/account/sections/connections.js +333 -0
- package/core/js/pages/dashboard/account/sections/data-request.js +225 -0
- package/core/js/pages/dashboard/account/sections/delete.js +80 -0
- package/core/js/pages/dashboard/account/sections/notifications.js +167 -0
- package/core/js/pages/dashboard/account/sections/orders.js +151 -0
- package/core/js/pages/dashboard/account/sections/profile.js +200 -0
- package/core/js/pages/dashboard/account/sections/referrals.js +217 -0
- package/core/js/pages/dashboard/account/sections/refund.js +253 -0
- package/core/js/pages/dashboard/account/sections/security.js +726 -0
- package/core/js/pages/dashboard/account/sections/team.js +247 -0
- package/core/js/pages/download/index.js +418 -0
- package/core/js/pages/extension/index.js +164 -0
- package/core/js/pages/extension/installed/index.js +135 -0
- package/core/js/pages/feedback/index.js +193 -0
- package/core/js/pages/legal/_document.js +91 -0
- package/core/js/pages/payment/checkout/index.js +420 -0
- package/core/js/pages/payment/checkout/modules/api.js +139 -0
- package/core/js/pages/payment/checkout/modules/dev-section.js +175 -0
- package/core/js/pages/payment/checkout/modules/discount.js +69 -0
- package/core/js/pages/payment/checkout/modules/pricing.js +73 -0
- package/core/js/pages/payment/checkout/modules/state.js +339 -0
- package/core/js/pages/payment/checkout/modules/tracking.js +54 -0
- package/core/js/pages/payment/confirmation/index.js +91 -0
- package/core/js/pages/payment/confirmation/modules/celebration.js +45 -0
- package/core/js/pages/payment/confirmation/modules/state.js +112 -0
- package/core/js/pages/payment/confirmation/modules/tracking.js +133 -0
- package/core/js/pages/payment/confirmation/modules/verify.js +142 -0
- package/core/js/pages/portal/email-preferences/index.js +183 -0
- package/core/js/pages/pricing/index.js +425 -0
- package/core/js/pages/privacy.js +2 -0
- package/core/js/pages/reset/index.js +12 -0
- package/core/js/pages/signin/index.js +22 -0
- package/core/js/pages/signup/index.js +23 -0
- package/core/js/pages/status/index.js +687 -0
- package/core/js/pages/terms.js +2 -0
- package/core/js/pages/test/components/[category]/[name]/index.js +26 -0
- package/core/js/pages/test/libraries/appearance/index.js +220 -0
- package/core/js/pages/test/libraries/form-manager/index.js +328 -0
- package/core/js/pages/test/libraries/layers/index.js +16 -0
- package/core/js/pages/test/libraries/verts/index.js +68 -0
- package/core/js/pages/test/sections/[category]/[name]/index.js +28 -0
- package/core/js/pages/test/styleguide/index.js +35 -0
- package/core/js/pages/token/index.js +203 -0
- package/core/logos/brandmarks/original/apple-app-store.svg +1 -0
- package/core/logos/brandmarks/original/apple.svg +6 -0
- package/core/logos/brandmarks/original/bitbucket.svg +14 -0
- package/core/logos/brandmarks/original/bitcoin.svg +1 -0
- package/core/logos/brandmarks/original/bluesky.svg +7 -0
- package/core/logos/brandmarks/original/bootstrap.svg +43 -0
- package/core/logos/brandmarks/original/brave.svg +1 -0
- package/core/logos/brandmarks/original/chrome.svg +1 -0
- package/core/logos/brandmarks/original/discord.svg +1 -0
- package/core/logos/brandmarks/original/dropbox.svg +10 -0
- package/core/logos/brandmarks/original/edge.svg +1 -0
- package/core/logos/brandmarks/original/facebook-f.svg +1 -0
- package/core/logos/brandmarks/original/facebook.svg +8 -0
- package/core/logos/brandmarks/original/figma.svg +10 -0
- package/core/logos/brandmarks/original/firefox.svg +1 -0
- package/core/logos/brandmarks/original/github.svg +6 -0
- package/core/logos/brandmarks/original/gitlab.svg +17 -0
- package/core/logos/brandmarks/original/google-a-s.svg +1 -0
- package/core/logos/brandmarks/original/google-chrome-webstore.svg +49 -0
- package/core/logos/brandmarks/original/google-play-store.svg +1 -0
- package/core/logos/brandmarks/original/google-web-search.svg +1 -0
- package/core/logos/brandmarks/original/google.svg +1 -0
- package/core/logos/brandmarks/original/gulp.svg +4 -0
- package/core/logos/brandmarks/original/html-5.svg +1 -0
- package/core/logos/brandmarks/original/instagram.svg +1 -0
- package/core/logos/brandmarks/original/javascript.svg +1 -0
- package/core/logos/brandmarks/original/jira.svg +18 -0
- package/core/logos/brandmarks/original/kick.svg +7 -0
- package/core/logos/brandmarks/original/linkedin.svg +1 -0
- package/core/logos/brandmarks/original/linux.svg +28 -0
- package/core/logos/brandmarks/original/messenger.svg +16 -0
- package/core/logos/brandmarks/original/microsoft.svg +1 -0
- package/core/logos/brandmarks/original/npm.svg +8 -0
- package/core/logos/brandmarks/original/opera.svg +1 -0
- package/core/logos/brandmarks/original/patreon.svg +6 -0
- package/core/logos/brandmarks/original/pinterest.svg +6 -0
- package/core/logos/brandmarks/original/reddit.svg +1 -0
- package/core/logos/brandmarks/original/safari.svg +31 -0
- package/core/logos/brandmarks/original/sass.svg +3 -0
- package/core/logos/brandmarks/original/signal.svg +6 -0
- package/core/logos/brandmarks/original/skype.svg +47 -0
- package/core/logos/brandmarks/original/slack.svg +33 -0
- package/core/logos/brandmarks/original/soundcloud.svg +1 -0
- package/core/logos/brandmarks/original/spotify.svg +5 -0
- package/core/logos/brandmarks/original/steam.svg +6 -0
- package/core/logos/brandmarks/original/telegram.svg +14 -0
- package/core/logos/brandmarks/original/tiktok.svg +1 -0
- package/core/logos/brandmarks/original/trello.svg +16 -0
- package/core/logos/brandmarks/original/tumblr.svg +1 -0
- package/core/logos/brandmarks/original/twitch.svg +6 -0
- package/core/logos/brandmarks/original/twitter.svg +6 -0
- package/core/logos/brandmarks/original/unity.svg +6 -0
- package/core/logos/brandmarks/original/x.svg +7 -0
- package/core/logos/brandmarks/original/yahoo-y.svg +1 -0
- package/core/logos/brandmarks/original/youtube-square.svg +1 -0
- package/core/logos/brandmarks/original/youtube.svg +1 -0
- package/core/logos/combomarks/dark/airbnb.svg +30 -0
- package/core/logos/combomarks/dark/slack.svg +38 -0
- package/core/logos/combomarks/dark/spotify.svg +35 -0
- package/core/logos/combomarks/dark/twilio.svg +26 -0
- package/core/logos/combomarks/dark/weebly.svg +35 -0
- package/core/logos/combomarks/gray/airbnb.svg +33 -0
- package/core/logos/combomarks/gray/alphabet.svg +23 -0
- package/core/logos/combomarks/gray/amazon.svg +38 -0
- package/core/logos/combomarks/gray/boring-company.svg +91 -0
- package/core/logos/combomarks/gray/burberry.svg +239 -0
- package/core/logos/combomarks/gray/embark.svg +21 -0
- package/core/logos/combomarks/gray/fitbit.svg +61 -0
- package/core/logos/combomarks/gray/fred-perry.svg +94 -0
- package/core/logos/combomarks/gray/google.svg +27 -0
- package/core/logos/combomarks/gray/hollister.svg +34 -0
- package/core/logos/combomarks/gray/hubspot.svg +27 -0
- package/core/logos/combomarks/gray/kaplan.svg +33 -0
- package/core/logos/combomarks/gray/lenovo.svg +27 -0
- package/core/logos/combomarks/gray/levis.svg +25 -0
- package/core/logos/combomarks/gray/mapbox.svg +37 -0
- package/core/logos/combomarks/gray/netflix.svg +15 -0
- package/core/logos/combomarks/gray/new-balance.svg +32 -0
- package/core/logos/combomarks/gray/nike.svg +17 -0
- package/core/logos/combomarks/gray/paypal.svg +41 -0
- package/core/logos/combomarks/gray/puma.svg +82 -0
- package/core/logos/combomarks/gray/samsung.svg +26 -0
- package/core/logos/combomarks/gray/shopify.svg +36 -0
- package/core/logos/combomarks/gray/slack.svg +42 -0
- package/core/logos/combomarks/gray/spotify.svg +38 -0
- package/core/logos/combomarks/gray/stripe.svg +24 -0
- package/core/logos/combomarks/gray/tnf.svg +30 -0
- package/core/logos/combomarks/gray/twilio.svg +29 -0
- package/core/logos/combomarks/gray/uber.svg +24 -0
- package/core/logos/combomarks/gray/weebly.svg +38 -0
- package/core/logos/combomarks/original/airbnb.svg +33 -0
- package/core/logos/combomarks/original/alphabet.svg +23 -0
- package/core/logos/combomarks/original/amazon.svg +38 -0
- package/core/logos/combomarks/original/asana.svg +8 -0
- package/core/logos/combomarks/original/boring-company.svg +91 -0
- package/core/logos/combomarks/original/discord.svg +6 -0
- package/core/logos/combomarks/original/embark.svg +24 -0
- package/core/logos/combomarks/original/fitbit.svg +60 -0
- package/core/logos/combomarks/original/github.svg +6 -0
- package/core/logos/combomarks/original/gitlab.svg +20 -0
- package/core/logos/combomarks/original/google.svg +28 -0
- package/core/logos/combomarks/original/hubspot.svg +29 -0
- package/core/logos/combomarks/original/instagram.svg +7 -0
- package/core/logos/combomarks/original/kickstarter.svg +6 -0
- package/core/logos/combomarks/original/lenovo.svg +24 -0
- package/core/logos/combomarks/original/linkedin.svg +6 -0
- package/core/logos/combomarks/original/mapbox.svg +39 -0
- package/core/logos/combomarks/original/mastodon.svg +7 -0
- package/core/logos/combomarks/original/medium.svg +6 -0
- package/core/logos/combomarks/original/meta.svg +22 -0
- package/core/logos/combomarks/original/microsoft.svg +13 -0
- package/core/logos/combomarks/original/monday.svg +12 -0
- package/core/logos/combomarks/original/netflix.svg +15 -0
- package/core/logos/combomarks/original/notion.svg +9 -0
- package/core/logos/combomarks/original/paypal.svg +41 -0
- package/core/logos/combomarks/original/reddit.svg +13 -0
- package/core/logos/combomarks/original/samsung.svg +25 -0
- package/core/logos/combomarks/original/shopify.svg +35 -0
- package/core/logos/combomarks/original/slack.svg +47 -0
- package/core/logos/combomarks/original/spotify.svg +37 -0
- package/core/logos/combomarks/original/stackoverflow.svg +28 -0
- package/core/logos/combomarks/original/stripe.svg +24 -0
- package/core/logos/combomarks/original/tiktok.svg +12 -0
- package/core/logos/combomarks/original/tumblr.svg +6 -0
- package/core/logos/combomarks/original/uber.svg +24 -0
- package/core/logos/combomarks/original/weebly.svg +34 -0
- package/core/logos/combomarks/original/whatsapp.svg +20 -0
- package/core/logos/combomarks/original/youtube.svg +10 -0
- package/core/logos/combomarks/original/zoom.svg +7 -0
- package/core/logos/combomarks/white/airbnb.svg +33 -0
- package/core/logos/combomarks/white/alphabet.svg +23 -0
- package/core/logos/combomarks/white/amazon.svg +38 -0
- package/core/logos/combomarks/white/boring-company.svg +91 -0
- package/core/logos/combomarks/white/embark.svg +21 -0
- package/core/logos/combomarks/white/fitbit.svg +61 -0
- package/core/logos/combomarks/white/google.svg +27 -0
- package/core/logos/combomarks/white/hubspot.svg +27 -0
- package/core/logos/combomarks/white/kaplan.svg +33 -0
- package/core/logos/combomarks/white/lenovo.svg +27 -0
- package/core/logos/combomarks/white/mapbox.svg +37 -0
- package/core/logos/combomarks/white/netflix.svg +15 -0
- package/core/logos/combomarks/white/paypal.svg +40 -0
- package/core/logos/combomarks/white/samsung.svg +26 -0
- package/core/logos/combomarks/white/shopify.svg +36 -0
- package/core/logos/combomarks/white/slack.svg +42 -0
- package/core/logos/combomarks/white/spotify.svg +38 -0
- package/core/logos/combomarks/white/stripe.svg +24 -0
- package/core/logos/combomarks/white/twilio.svg +29 -0
- package/core/logos/combomarks/white/uber.svg +24 -0
- package/core/logos/combomarks/white/weebly.svg +38 -0
- package/defaults/pages/404.md +4 -0
- package/defaults/pages/about.md +4 -0
- package/defaults/pages/admin/calendar/index.html +4 -0
- package/defaults/pages/admin/firebase/index.html +4 -0
- package/defaults/pages/admin/index.md +4 -0
- package/defaults/pages/admin/posts/editor.html +4 -0
- package/defaults/pages/admin/posts/index.html +4 -0
- package/defaults/pages/admin/stackblitz/index.html +4 -0
- package/defaults/pages/admin/studio/index.html +4 -0
- package/defaults/pages/admin/users/index.html +4 -0
- package/defaults/pages/admin/users/new.html +4 -0
- package/defaults/pages/admin/verts/index.html +4 -0
- package/defaults/pages/ads-txt.html +11 -0
- package/defaults/pages/alternatives/index.md +4 -0
- package/defaults/pages/app.md +4 -0
- package/defaults/pages/auth/reset.md +4 -0
- package/defaults/pages/auth/signin.md +4 -0
- package/defaults/pages/auth/signup.md +4 -0
- package/defaults/pages/auth/token.md +4 -0
- package/defaults/pages/blog/categories/category.html +21 -0
- package/defaults/pages/blog/categories.md +4 -0
- package/defaults/pages/blog/index-json.html +27 -0
- package/defaults/pages/blog/tags/tag.html +21 -0
- package/defaults/pages/blog/tags.md +4 -0
- package/defaults/pages/blog.md +22 -0
- package/defaults/pages/careers.md +4 -0
- package/defaults/pages/connections/callback.md +4 -0
- package/defaults/pages/contact.md +4 -0
- package/defaults/pages/dashboard/account.md +4 -0
- package/defaults/pages/dashboard/index.md +12 -0
- package/defaults/pages/download.md +4 -0
- package/defaults/pages/extension/index.html +4 -0
- package/defaults/pages/extension/installed.html +4 -0
- package/defaults/pages/feedback.md +4 -0
- package/defaults/pages/feeds/posts-json.html +40 -0
- package/defaults/pages/feeds/posts-xml.html +38 -0
- package/defaults/pages/humans.html +25 -0
- package/defaults/pages/index.md +4 -0
- package/defaults/pages/legal/cookies.md +4 -0
- package/defaults/pages/legal/privacy.md +4 -0
- package/defaults/pages/legal/terms.md +4 -0
- package/defaults/pages/llms-txt.html +46 -0
- package/defaults/pages/opensearch.html +16 -0
- package/defaults/pages/pages-json.html +42 -0
- package/defaults/pages/payment/checkout.md +4 -0
- package/defaults/pages/payment/confirmation.md +4 -0
- package/defaults/pages/portal/email-preferences.md +4 -0
- package/defaults/pages/pricing.md +4 -0
- package/defaults/pages/redirects/account.md +12 -0
- package/defaults/pages/redirects/admin/dashboard.md +11 -0
- package/defaults/pages/redirects/auth/change-password.md +9 -0
- package/defaults/pages/redirects/auth/forgot.md +9 -0
- package/defaults/pages/redirects/auth/join.md +9 -0
- package/defaults/pages/redirects/auth/login.md +9 -0
- package/defaults/pages/redirects/auth/recover.md +9 -0
- package/defaults/pages/redirects/auth/register.md +9 -0
- package/defaults/pages/redirects/auth/reset-password.md +9 -0
- package/defaults/pages/redirects/billing/cancel.md +9 -0
- package/defaults/pages/redirects/billing/refund.md +9 -0
- package/defaults/pages/robots.html +36 -0
- package/defaults/pages/security-txt.html +11 -0
- package/defaults/pages/sitemap.html +37 -0
- package/defaults/pages/status.md +4 -0
- package/defaults/pages/team/index.md +4 -0
- package/defaults/pages/test/account/dashboard.html +16 -0
- package/defaults/pages/test/index.md +12 -0
- package/defaults/pages/test/libraries/appearance.html +268 -0
- package/defaults/pages/test/libraries/cover.html +50 -0
- package/defaults/pages/test/libraries/error.html +30 -0
- package/defaults/pages/test/libraries/firestore.html +202 -0
- package/defaults/pages/test/libraries/form-manager.html +374 -0
- package/defaults/pages/test/libraries/layers.html +52 -0
- package/defaults/pages/test/libraries/lazy-loading.html +450 -0
- package/defaults/pages/test/libraries/verts.html +149 -0
- package/defaults/pages/test/redirect/external.md +13 -0
- package/defaults/pages/test/redirect/internal.md +13 -0
- package/defaults/pages/test/styleguide/index.html +588 -0
- package/defaults/pages/test/translation/index.md +50 -0
- package/defaults/pages/updates/index.md +4 -0
- package/defaults/sample-posts/2026-01-12-welcome-to-the-blog.md +26 -0
- package/defaults/sample-posts/2026-02-03-a-field-guide-to-good-defaults.md +50 -0
- package/defaults/sample-posts/2026-02-24-shipping-week-notes.md +37 -0
- package/defaults/sample-posts/2026-03-17-the-case-for-one-config.md +36 -0
- package/defaults/sample-posts/2026-04-09-write-release-notes-people-read.md +34 -0
- package/defaults/sample-posts/2026-05-21-dark-mode-done-properly.md +38 -0
- package/defaults/sample-posts/2026-06-02-the-boring-deploy-is-the-good-deploy.md +30 -0
- package/defaults/sample-posts/2026-06-11-naming-things-a-survival-guide.md +30 -0
- package/defaults/sample-posts/2026-06-19-your-pricing-page-is-a-product.md +28 -0
- package/defaults/sample-posts/2026-06-30-email-is-an-api-with-feelings.md +28 -0
- package/defaults/sample-posts/2026-07-08-ship-the-docs-with-the-diff.md +30 -0
- package/defaults/sample-team/avery-quinn.md +22 -0
- package/defaults/sample-team/jordan-reyes.md +25 -0
- package/defaults/sample-team/riley-nakamura.md +22 -0
- package/defaults/sample-team/sam-okafor.md +22 -0
- package/defaults/sample-updates/v1.0.0.md +28 -0
- package/defaults/sample-updates/v1.1.0.md +27 -0
- package/defaults/sample-updates/v1.2.0.md +27 -0
- package/defaults/sample-updates/v1.3.0.md +28 -0
- package/defaults/showcase/components.html +62 -0
- package/defaults/showcase/entry.html +76 -0
- package/defaults/showcase/frame.html +62 -0
- package/defaults/showcase/index.html +59 -0
- package/dist/assets.js +1349 -0
- package/dist/brand-tokens.js +195 -0
- package/dist/build.js +192 -0
- package/dist/cachebreak-html.js +71 -0
- package/dist/cli-run.js +25 -0
- package/dist/cli.js +47 -0
- package/dist/collections.js +183 -0
- package/dist/commands/audit.js +470 -0
- package/dist/commands/build.js +197 -0
- package/dist/commands/clean.js +18 -0
- package/dist/commands/customize.js +125 -0
- package/dist/commands/deploy.js +441 -0
- package/dist/commands/dev.js +1067 -0
- package/dist/commands/install.js +31 -0
- package/dist/commands/lib/deploy-precheck.js +43 -0
- package/dist/commands/lib/ensure-target.js +115 -0
- package/dist/commands/migrate.js +86 -0
- package/dist/commands/purge.js +31 -0
- package/dist/commands/test.js +237 -0
- package/dist/commands/translate.js +57 -0
- package/dist/commands/update.js +22 -0
- package/dist/commands/version.js +11 -0
- package/dist/config-sections.js +245 -0
- package/dist/consumer-scan.js +118 -0
- package/dist/consumer.js +49 -0
- package/dist/customize.js +328 -0
- package/dist/decisions.js +153 -0
- package/dist/dist-audit.js +417 -0
- package/dist/dynamic-pages.js +357 -0
- package/dist/engine.js +1402 -0
- package/dist/firebase-auth-helpers.js +198 -0
- package/dist/font-metrics.js +420 -0
- package/dist/frontmatter-liquid.js +253 -0
- package/dist/github-secrets.js +80 -0
- package/dist/hero-animations.js +170 -0
- package/dist/imagemin.js +261 -0
- package/dist/index.js +41 -0
- package/dist/inline-icons.js +65 -0
- package/dist/language-flags.js +55 -0
- package/dist/layers.js +80 -0
- package/dist/layouts.js +44 -0
- package/dist/limit-collections.js +199 -0
- package/dist/link-resolver.js +303 -0
- package/dist/markdown-images.js +85 -0
- package/dist/merge.js +20 -0
- package/dist/migrate/codemod.js +172 -0
- package/dist/migrate/config-convert.js +448 -0
- package/dist/migrate/consumer-assets.js +130 -0
- package/dist/migrate/index.js +223 -0
- package/dist/migrate/lint.js +108 -0
- package/dist/migrate/rules.js +1929 -0
- package/dist/minify-html.js +148 -0
- package/dist/mode-helpers.js +96 -0
- package/dist/omega-bin.js +7 -0
- package/dist/overrides.js +190 -0
- package/dist/path-prefix.js +167 -0
- package/dist/paths.js +37 -0
- package/dist/pricing.js +301 -0
- package/dist/purge.js +95 -0
- package/dist/redirect-page.js +45 -0
- package/dist/sample-content.js +210 -0
- package/dist/scaffold.js +123 -0
- package/dist/sections.js +810 -0
- package/dist/service-worker.js +200 -0
- package/dist/social-pages.js +142 -0
- package/dist/srcset.js +67 -0
- package/dist/static-assets.js +154 -0
- package/dist/target-shortlinks.js +123 -0
- package/dist/theme-vocabulary.js +76 -0
- package/dist/translate/collect-text-nodes.js +93 -0
- package/dist/translate/default-routes.js +152 -0
- package/dist/translate/generate-defaults.js +297 -0
- package/dist/translate/index.js +568 -0
- package/dist/translate/packaged-defaults.js +127 -0
- package/dist/translate/sitemap.js +108 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/actions-secrets.js +145 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/bare-requires.js +153 -0
- package/dist/vendor/devkit/bundle.js +292 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/defaults-engine.js +372 -0
- package/dist/vendor/devkit/deploy-precheck.js +54 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/deploy.js +286 -0
- package/dist/vendor/devkit/env-watch.js +132 -0
- package/dist/vendor/devkit/framework-deps.js +46 -0
- package/dist/vendor/devkit/icons.js +178 -0
- package/dist/vendor/devkit/license.js +155 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/merge-line-files.js +296 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/reads.js +274 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/scaffold-guard.js +96 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/strip-dev-blocks-plugin.js +29 -0
- package/dist/vendor/devkit/strip-dev-blocks.js +32 -0
- package/dist/vendor/devkit/target-secrets.js +165 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/dist/vendor/template-kit/data/language-flags.js +54 -0
- package/dist/vendor/template-kit/data/languages.js +195 -0
- package/dist/vendor/template-kit/data/social-urls.js +26 -0
- package/dist/vendor/template-kit/filters.js +227 -0
- package/dist/vendor/template-kit/index.js +28 -0
- package/dist/vendor/template-kit/jekyll-compat.js +268 -0
- package/dist/vendor/template-kit/register-liquid.js +0 -0
- package/dist/vendor/template-kit/tags/collections.js +191 -0
- package/dist/vendor/template-kit/tags/conditionals.js +101 -0
- package/dist/vendor/template-kit/tags/content.js +175 -0
- package/dist/vendor/template-kit/tags/index.js +33 -0
- package/dist/vendor/template-kit/tags/media.js +376 -0
- package/dist/vendor/template-kit/variable-resolver.js +163 -0
- package/docs/ads-system.md +109 -0
- package/docs/classy-v2/DIRECTION.md +92 -0
- package/docs/classy-v2/draft-1.html +1932 -0
- package/docs/classy-v2/draft-2.html +1956 -0
- package/docs/frontmatter.md +153 -0
- package/docs/index.md +254 -0
- package/docs/libs.md +298 -0
- package/docs/omega-sections-spec.md +159 -0
- package/docs/page-contract.md +112 -0
- package/docs/sections.md +845 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/template-kit.md +128 -0
- package/docs/test-framework.md +163 -0
- package/package.json +122 -0
- package/runtime/boot.js +149 -0
- package/runtime/icons.js +77 -0
- package/runtime/manager.js +36 -0
- package/scaffold/.github/workflows/build.yml +139 -0
- package/scaffold/.nvmrc +1 -0
- package/scaffold/AGENTS.md +55 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/_.gitattributes +4 -0
- package/scaffold/_.gitignore +27 -0
- package/scaffold/config/omega.json5 +20 -0
- package/scaffold/src/assets/css/pages/.gitkeep +0 -0
- package/scaffold/src/assets/images/.gitkeep +0 -0
- package/scaffold/src/assets/js/pages/.gitkeep +0 -0
- package/scaffold/src/pages/.gitkeep +0 -0
- package/scaffold/src/pages/example.md.txt +94 -0
- package/scaffold/src/service-worker.js +16 -0
- package/sw/entry.js +19 -0
- package/sw/manager.js +281 -0
- package/themes/_template/README.md +115 -0
- package/themes/_template/_config.scss +60 -0
- package/themes/_template/_theme.js +14 -0
- package/themes/_template/_theme.scss +28 -0
- package/themes/_template/css/base/_root.scss +19 -0
- package/themes/_template/css/components/_components.scss +23 -0
- package/themes/base/README.md +5 -0
- package/themes/base/_components/data/org-chart/component.html +33 -0
- package/themes/base/_components/data/org-chart/component.json5 +33 -0
- package/themes/base/_components/data/org-chart/component.scss +183 -0
- package/themes/base/_components/heading/masthead/component.html +22 -0
- package/themes/base/_components/heading/masthead/component.json5 +30 -0
- package/themes/base/_components/heading/section-head/component.html +18 -0
- package/themes/base/_components/heading/section-head/component.json5 +27 -0
- package/themes/base/_components/pricing/features/component.html +29 -0
- package/themes/base/_components/pricing/features/component.json5 +30 -0
- package/themes/base/_hero/orbit/index.html +30 -0
- package/themes/base/_hero/orbit/script.js +33 -0
- package/themes/base/_hero/orbit/style.scss +133 -0
- package/themes/base/_includes/admin/sections/sidebar.html +3 -0
- package/themes/base/_includes/admin/sections/topbar.html +3 -0
- package/themes/base/_includes/backend/sections/sidebar.html +3 -0
- package/themes/base/_includes/backend/sections/topbar.html +3 -0
- package/themes/base/_includes/frontend/components/connection-card.html +49 -0
- package/themes/base/_includes/frontend/components/post-card.html +54 -0
- package/themes/base/_includes/frontend/sections/account-connections.html +42 -0
- package/themes/base/_includes/frontend/sections/account-section-header.html +11 -0
- package/themes/base/_includes/frontend/sections/footer.html +154 -0
- package/themes/base/_includes/frontend/sections/nav.html +276 -0
- package/themes/base/_includes/global/sections/account.html +72 -0
- package/themes/base/_includes/global/sections/app-sidebar.html +248 -0
- package/themes/base/_includes/global/sections/app-topbar.html +228 -0
- package/themes/base/_includes/global/sections/page-header.html +99 -0
- package/themes/base/_layouts/admin/core/minimal-viewport-locked.html +22 -0
- package/themes/base/_layouts/admin/core/minimal.html +23 -0
- package/themes/base/_layouts/backend/core/base.html +57 -0
- package/themes/base/_layouts/backend/core/minimal-viewport-locked.html +32 -0
- package/themes/base/_layouts/backend/core/minimal.html +32 -0
- package/themes/base/_layouts/backend/pages/dashboard/index.html +230 -0
- package/themes/base/_layouts/frontend/core/base.html +31 -0
- package/themes/base/_layouts/frontend/core/cover.html +40 -0
- package/themes/base/_layouts/frontend/core/minimal.html +47 -0
- package/themes/base/_layouts/frontend/pages/404.html +54 -0
- package/themes/base/_layouts/frontend/pages/about.html +144 -0
- package/themes/base/_layouts/frontend/pages/account/index.html +2104 -0
- package/themes/base/_layouts/frontend/pages/alternatives/alternative.html +319 -0
- package/themes/base/_layouts/frontend/pages/alternatives/index.html +186 -0
- package/themes/base/_layouts/frontend/pages/app.html +142 -0
- package/themes/base/_layouts/frontend/pages/auth/reset.html +53 -0
- package/themes/base/_layouts/frontend/pages/auth/signin.html +90 -0
- package/themes/base/_layouts/frontend/pages/auth/signup.html +141 -0
- package/themes/base/_layouts/frontend/pages/auth/token.html +44 -0
- package/themes/base/_layouts/frontend/pages/blog/categories/category.html +88 -0
- package/themes/base/_layouts/frontend/pages/blog/categories/index.html +88 -0
- package/themes/base/_layouts/frontend/pages/blog/index.html +255 -0
- package/themes/base/_layouts/frontend/pages/blog/post.html +261 -0
- package/themes/base/_layouts/frontend/pages/blog/tags/index.html +84 -0
- package/themes/base/_layouts/frontend/pages/blog/tags/tag.html +88 -0
- package/themes/base/_layouts/frontend/pages/collection/category.html +72 -0
- package/themes/base/_layouts/frontend/pages/collection/document.html +54 -0
- package/themes/base/_layouts/frontend/pages/collection/index.html +84 -0
- package/themes/base/_layouts/frontend/pages/connections/callback.html +55 -0
- package/themes/base/_layouts/frontend/pages/contact.html +214 -0
- package/themes/base/_layouts/frontend/pages/download.html +793 -0
- package/themes/base/_layouts/frontend/pages/extension/index.html +253 -0
- package/themes/base/_layouts/frontend/pages/extension/installed.html +198 -0
- package/themes/base/_layouts/frontend/pages/feedback.html +171 -0
- package/themes/base/_layouts/frontend/pages/index.html +66 -0
- package/themes/base/_layouts/frontend/pages/legal/document.html +78 -0
- package/themes/base/_layouts/frontend/pages/payment/checkout.html +459 -0
- package/themes/base/_layouts/frontend/pages/payment/confirmation.html +131 -0
- package/themes/base/_layouts/frontend/pages/portal/email-preferences.html +104 -0
- package/themes/base/_layouts/frontend/pages/pricing.html +596 -0
- package/themes/base/_layouts/frontend/pages/status.html +361 -0
- package/themes/base/_layouts/frontend/pages/team/index.html +210 -0
- package/themes/base/_layouts/frontend/pages/team/member.html +125 -0
- package/themes/base/_layouts/frontend/pages/updates/index.html +119 -0
- package/themes/base/_layouts/frontend/pages/updates/update.html +118 -0
- package/themes/base/_sections/about/hero/section.html +68 -0
- package/themes/base/_sections/about/hero/section.json5 +58 -0
- package/themes/base/_sections/about/letter/section.html +60 -0
- package/themes/base/_sections/about/letter/section.json5 +56 -0
- package/themes/base/_sections/about/photo-band/section.html +24 -0
- package/themes/base/_sections/about/photo-band/section.json5 +23 -0
- package/themes/base/_sections/about/principles/section.html +17 -0
- package/themes/base/_sections/about/principles/section.json5 +34 -0
- package/themes/base/_sections/about/timeline/section.html +20 -0
- package/themes/base/_sections/about/timeline/section.json5 +34 -0
- package/themes/base/_sections/marketing/bento/section.html +91 -0
- package/themes/base/_sections/marketing/bento/section.json5 +152 -0
- package/themes/base/_sections/marketing/cta/section.html +44 -0
- package/themes/base/_sections/marketing/cta/section.json5 +47 -0
- package/themes/base/_sections/marketing/faq/section.html +52 -0
- package/themes/base/_sections/marketing/faq/section.json5 +68 -0
- package/themes/base/_sections/marketing/hero/section.html +349 -0
- package/themes/base/_sections/marketing/hero/section.json5 +335 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.html +38 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.js +86 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.json5 +29 -0
- package/themes/base/_sections/marketing/pricing-cards/section.html +115 -0
- package/themes/base/_sections/marketing/pricing-cards/section.json5 +72 -0
- package/themes/base/_sections/marketing/product-demo/section.html +62 -0
- package/themes/base/_sections/marketing/product-demo/section.js +44 -0
- package/themes/base/_sections/marketing/product-demo/section.json5 +52 -0
- package/themes/base/_sections/marketing/prose/section.html +22 -0
- package/themes/base/_sections/marketing/prose/section.json5 +41 -0
- package/themes/base/_sections/marketing/showcase/section.html +35 -0
- package/themes/base/_sections/marketing/showcase/section.json5 +52 -0
- package/themes/base/_sections/marketing/stats/section.html +32 -0
- package/themes/base/_sections/marketing/stats/section.json5 +44 -0
- package/themes/base/_sections/marketing/testimonials/section.html +54 -0
- package/themes/base/_sections/marketing/testimonials/section.json5 +70 -0
- package/themes/base/_sections/marketing/trusted-by/section.html +30 -0
- package/themes/base/_sections/marketing/trusted-by/section.json5 +38 -0
- package/themes/base/_sections/verts/unit/section.html +7 -0
- package/themes/base/_sections/verts/unit/section.js +22 -0
- package/themes/base/_sections/verts/unit/section.json5 +24 -0
- package/themes/base/_sections/verts/unit/section.scss +24 -0
- package/themes/base/_theme.js +13 -0
- package/themes/base/_theme.scss +4 -0
- package/themes/bootstrap/_theme.js +16 -0
- package/themes/bootstrap/_theme.scss +11 -0
- package/themes/bootstrap/js/index.esm.js +19 -0
- package/themes/bootstrap/js/index.umd.js +34 -0
- package/themes/bootstrap/js/src/alert.js +87 -0
- package/themes/bootstrap/js/src/base-component.js +86 -0
- package/themes/bootstrap/js/src/button.js +72 -0
- package/themes/bootstrap/js/src/carousel.js +474 -0
- package/themes/bootstrap/js/src/collapse.js +297 -0
- package/themes/bootstrap/js/src/dom/data.js +55 -0
- package/themes/bootstrap/js/src/dom/event-handler.js +317 -0
- package/themes/bootstrap/js/src/dom/manipulator.js +71 -0
- package/themes/bootstrap/js/src/dom/selector-engine.js +126 -0
- package/themes/bootstrap/js/src/dropdown.js +458 -0
- package/themes/bootstrap/js/src/modal.js +378 -0
- package/themes/bootstrap/js/src/offcanvas.js +282 -0
- package/themes/bootstrap/js/src/popover.js +97 -0
- package/themes/bootstrap/js/src/scrollspy.js +296 -0
- package/themes/bootstrap/js/src/tab.js +315 -0
- package/themes/bootstrap/js/src/toast.js +224 -0
- package/themes/bootstrap/js/src/tooltip.js +632 -0
- package/themes/bootstrap/js/src/util/backdrop.js +151 -0
- package/themes/bootstrap/js/src/util/component-functions.js +35 -0
- package/themes/bootstrap/js/src/util/config.js +65 -0
- package/themes/bootstrap/js/src/util/focustrap.js +115 -0
- package/themes/bootstrap/js/src/util/index.js +306 -0
- package/themes/bootstrap/js/src/util/sanitizer.js +117 -0
- package/themes/bootstrap/js/src/util/scrollbar.js +114 -0
- package/themes/bootstrap/js/src/util/swipe.js +146 -0
- package/themes/bootstrap/js/src/util/template-factory.js +160 -0
- package/themes/bootstrap/overrides/_accordion.scss +21 -0
- package/themes/bootstrap/overrides/_avatars.scss +51 -0
- package/themes/bootstrap/overrides/_blog-images.scss +45 -0
- package/themes/bootstrap/overrides/_buttons-adaptive.scss +141 -0
- package/themes/bootstrap/overrides/_color-shades.scss +25 -0
- package/themes/bootstrap/overrides/_cursor-utilities.scss +13 -0
- package/themes/bootstrap/overrides/_index.scss +30 -0
- package/themes/bootstrap/overrides/_links.scss +26 -0
- package/themes/bootstrap/overrides/_soft-colors.scss +99 -0
- package/themes/bootstrap/overrides/_spacing-extended.scss +74 -0
- package/themes/bootstrap/overrides/_spinners.scss +22 -0
- package/themes/bootstrap/overrides/_typography-utilities.scss +40 -0
- package/themes/bootstrap/scss/_accordion.scss +153 -0
- package/themes/bootstrap/scss/_alert.scss +68 -0
- package/themes/bootstrap/scss/_badge.scss +38 -0
- package/themes/bootstrap/scss/_breadcrumb.scss +40 -0
- package/themes/bootstrap/scss/_button-group.scss +147 -0
- package/themes/bootstrap/scss/_buttons.scss +216 -0
- package/themes/bootstrap/scss/_card.scss +238 -0
- package/themes/bootstrap/scss/_carousel.scss +226 -0
- package/themes/bootstrap/scss/_close.scss +66 -0
- package/themes/bootstrap/scss/_containers.scss +41 -0
- package/themes/bootstrap/scss/_dropdown.scss +250 -0
- package/themes/bootstrap/scss/_forms.scss +9 -0
- package/themes/bootstrap/scss/_functions.scss +302 -0
- package/themes/bootstrap/scss/_grid.scss +39 -0
- package/themes/bootstrap/scss/_helpers.scss +12 -0
- package/themes/bootstrap/scss/_images.scss +42 -0
- package/themes/bootstrap/scss/_list-group.scss +199 -0
- package/themes/bootstrap/scss/_maps.scss +174 -0
- package/themes/bootstrap/scss/_mixins.scss +42 -0
- package/themes/bootstrap/scss/_modal.scss +240 -0
- package/themes/bootstrap/scss/_nav.scss +197 -0
- package/themes/bootstrap/scss/_navbar.scss +289 -0
- package/themes/bootstrap/scss/_offcanvas.scss +147 -0
- package/themes/bootstrap/scss/_pagination.scss +109 -0
- package/themes/bootstrap/scss/_placeholders.scss +51 -0
- package/themes/bootstrap/scss/_popover.scss +196 -0
- package/themes/bootstrap/scss/_progress.scss +68 -0
- package/themes/bootstrap/scss/_reboot.scss +611 -0
- package/themes/bootstrap/scss/_root.scss +187 -0
- package/themes/bootstrap/scss/_spinners.scss +85 -0
- package/themes/bootstrap/scss/_tables.scss +171 -0
- package/themes/bootstrap/scss/_toasts.scss +73 -0
- package/themes/bootstrap/scss/_tooltip.scss +119 -0
- package/themes/bootstrap/scss/_transitions.scss +27 -0
- package/themes/bootstrap/scss/_type.scss +106 -0
- package/themes/bootstrap/scss/_utilities.scss +806 -0
- package/themes/bootstrap/scss/_variables-dark.scss +102 -0
- package/themes/bootstrap/scss/_variables.scss +1753 -0
- package/themes/bootstrap/scss/bootstrap-grid.scss +62 -0
- package/themes/bootstrap/scss/bootstrap-reboot.scss +10 -0
- package/themes/bootstrap/scss/bootstrap-utilities.scss +19 -0
- package/themes/bootstrap/scss/bootstrap.scss +52 -0
- package/themes/bootstrap/scss/forms/_floating-labels.scss +97 -0
- package/themes/bootstrap/scss/forms/_form-check.scss +189 -0
- package/themes/bootstrap/scss/forms/_form-control.scss +214 -0
- package/themes/bootstrap/scss/forms/_form-range.scss +91 -0
- package/themes/bootstrap/scss/forms/_form-select.scss +80 -0
- package/themes/bootstrap/scss/forms/_form-text.scss +11 -0
- package/themes/bootstrap/scss/forms/_input-group.scss +132 -0
- package/themes/bootstrap/scss/forms/_labels.scss +36 -0
- package/themes/bootstrap/scss/forms/_validation.scss +12 -0
- package/themes/bootstrap/scss/helpers/_clearfix.scss +3 -0
- package/themes/bootstrap/scss/helpers/_color-bg.scss +7 -0
- package/themes/bootstrap/scss/helpers/_colored-links.scss +30 -0
- package/themes/bootstrap/scss/helpers/_focus-ring.scss +5 -0
- package/themes/bootstrap/scss/helpers/_icon-link.scss +25 -0
- package/themes/bootstrap/scss/helpers/_position.scss +36 -0
- package/themes/bootstrap/scss/helpers/_ratio.scss +26 -0
- package/themes/bootstrap/scss/helpers/_stacks.scss +15 -0
- package/themes/bootstrap/scss/helpers/_stretched-link.scss +15 -0
- package/themes/bootstrap/scss/helpers/_text-truncation.scss +7 -0
- package/themes/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
- package/themes/bootstrap/scss/helpers/_vr.scss +8 -0
- package/themes/bootstrap/scss/mixins/_alert.scss +18 -0
- package/themes/bootstrap/scss/mixins/_backdrop.scss +14 -0
- package/themes/bootstrap/scss/mixins/_banner.scss +7 -0
- package/themes/bootstrap/scss/mixins/_border-radius.scss +78 -0
- package/themes/bootstrap/scss/mixins/_box-shadow.scss +18 -0
- package/themes/bootstrap/scss/mixins/_breakpoints.scss +127 -0
- package/themes/bootstrap/scss/mixins/_buttons.scss +70 -0
- package/themes/bootstrap/scss/mixins/_caret.scss +69 -0
- package/themes/bootstrap/scss/mixins/_clearfix.scss +9 -0
- package/themes/bootstrap/scss/mixins/_color-mode.scss +21 -0
- package/themes/bootstrap/scss/mixins/_color-scheme.scss +7 -0
- package/themes/bootstrap/scss/mixins/_container.scss +11 -0
- package/themes/bootstrap/scss/mixins/_deprecate.scss +10 -0
- package/themes/bootstrap/scss/mixins/_forms.scss +163 -0
- package/themes/bootstrap/scss/mixins/_gradients.scss +47 -0
- package/themes/bootstrap/scss/mixins/_grid.scss +151 -0
- package/themes/bootstrap/scss/mixins/_image.scss +16 -0
- package/themes/bootstrap/scss/mixins/_list-group.scss +26 -0
- package/themes/bootstrap/scss/mixins/_lists.scss +7 -0
- package/themes/bootstrap/scss/mixins/_pagination.scss +10 -0
- package/themes/bootstrap/scss/mixins/_reset-text.scss +17 -0
- package/themes/bootstrap/scss/mixins/_resize.scss +6 -0
- package/themes/bootstrap/scss/mixins/_table-variants.scss +24 -0
- package/themes/bootstrap/scss/mixins/_text-truncate.scss +8 -0
- package/themes/bootstrap/scss/mixins/_transition.scss +26 -0
- package/themes/bootstrap/scss/mixins/_utilities.scss +97 -0
- package/themes/bootstrap/scss/mixins/_visually-hidden.scss +38 -0
- package/themes/bootstrap/scss/tests/jasmine.js +16 -0
- package/themes/bootstrap/scss/tests/mixins/_auto-import-of-variables-dark.test.scss +7 -0
- package/themes/bootstrap/scss/tests/mixins/_color-modes.test.scss +69 -0
- package/themes/bootstrap/scss/tests/mixins/_media-query-color-mode-full.test.scss +8 -0
- package/themes/bootstrap/scss/tests/mixins/_utilities.test.scss +393 -0
- package/themes/bootstrap/scss/tests/sass-true/register.js +14 -0
- package/themes/bootstrap/scss/tests/sass-true/runner.js +17 -0
- package/themes/bootstrap/scss/tests/utilities/_api.test.scss +75 -0
- package/themes/bootstrap/scss/utilities/_api.scss +47 -0
- package/themes/bootstrap/scss/vendor/_rfs.scss +348 -0
- package/themes/classy/README.md +89 -0
- package/themes/classy/_config.scss +245 -0
- package/themes/classy/_theme.js +31 -0
- package/themes/classy/_theme.scss +57 -0
- package/themes/classy/css/app/_panels.scss +183 -0
- package/themes/classy/css/base/_fonts.scss +79 -0
- package/themes/classy/css/base/_no-raise.scss +49 -0
- package/themes/classy/css/base/_root.scss +113 -0
- package/themes/classy/css/base/_typography.scss +154 -0
- package/themes/classy/css/base/_utilities.scss +284 -0
- package/themes/classy/css/components/_alerts.scss +51 -0
- package/themes/classy/css/components/_badges.scss +46 -0
- package/themes/classy/css/components/_buttons.scss +175 -0
- package/themes/classy/css/components/_cards.scss +41 -0
- package/themes/classy/css/components/_dropdowns.scss +48 -0
- package/themes/classy/css/components/_forms.scss +171 -0
- package/themes/classy/css/components/_receipt.scss +45 -0
- package/themes/classy/css/layout/_footer.scss +298 -0
- package/themes/classy/css/layout/_general.scss +209 -0
- package/themes/classy/css/layout/_nav.scss +154 -0
- package/themes/classy/css/layout/_shell.scss +458 -0
- package/themes/classy/css/marketing/_bento.scss +238 -0
- package/themes/classy/css/marketing/_content.scss +1328 -0
- package/themes/classy/css/marketing/_downloads.scss +246 -0
- package/themes/classy/css/marketing/_hero.scss +311 -0
- package/themes/classy/css/marketing/_sections.scss +649 -0
- package/themes/classy/css/pages/404/index.scss +67 -0
- package/themes/classy/css/pages/_auth.scss +235 -0
- package/themes/classy/css/pages/admin/firebase/index.scss +22 -0
- package/themes/classy/css/pages/blog/[slug].scss +56 -0
- package/themes/classy/css/pages/cookies.scss +2 -0
- package/themes/classy/css/pages/dashboard/account/index.scss +31 -0
- package/themes/classy/css/pages/feedback/index.scss +39 -0
- package/themes/classy/css/pages/legal/_document.scss +192 -0
- package/themes/classy/css/pages/payment/checkout/index.scss +585 -0
- package/themes/classy/css/pages/payment/confirmation/index.scss +9 -0
- package/themes/classy/css/pages/privacy.scss +2 -0
- package/themes/classy/css/pages/status/index.scss +164 -0
- package/themes/classy/css/pages/terms.scss +2 -0
- package/themes/classy/css/pages/updates/index.scss +128 -0
- package/themes/classy/fonts/inter-italic-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/inter-italic-latin.woff2 +0 -0
- package/themes/classy/fonts/inter-normal-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/inter-normal-latin.woff2 +0 -0
- package/themes/classy/fonts/newsreader-italic-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/newsreader-italic-latin.woff2 +0 -0
- package/themes/classy/fonts/newsreader-normal-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/newsreader-normal-latin.woff2 +0 -0
- package/themes/classy/js/hero-demo-form.js +56 -0
- package/themes/neobrutalism/README.md +118 -0
- package/themes/neobrutalism/_config.scss +139 -0
- package/themes/neobrutalism/_layouts/frontend/core/base.html +30 -0
- package/themes/neobrutalism/_layouts/frontend/pages/index.html +275 -0
- package/themes/neobrutalism/_theme.js +24 -0
- package/themes/neobrutalism/_theme.scss +61 -0
- package/themes/neobrutalism/css/base/_mixins.scss +46 -0
- package/themes/neobrutalism/css/base/_root.scss +169 -0
- package/themes/neobrutalism/css/base/_typography.scss +61 -0
- package/themes/neobrutalism/css/base/_utilities.scss +89 -0
- package/themes/neobrutalism/css/components/_billing-toggle.scss +74 -0
- package/themes/neobrutalism/css/components/_buttons.scss +148 -0
- package/themes/neobrutalism/css/components/_cards.scss +47 -0
- package/themes/neobrutalism/css/components/_forms.scss +88 -0
- package/themes/neobrutalism/css/components/_marketing.scss +244 -0
- package/themes/neobrutalism/css/layout/_general.scss +213 -0
- package/themes/neobrutalism/css/layout/_navigation.scss +171 -0
- package/themes/neobrutalism/css/pages/index.scss +203 -0
- package/themes/neobrutalism/css/pages/pricing/index.scss +58 -0
- package/themes/neobrutalism/css/pages/test/libraries/layers/index.scss +7 -0
- package/themes/neobrutalism/js/pages/test/libraries/layers/index.js +9 -0
- package/themes/newsflash/README.md +117 -0
- package/themes/newsflash/_components/heading/lede/component.html +14 -0
- package/themes/newsflash/_components/heading/lede/component.json5 +23 -0
- package/themes/newsflash/_components/heading/rule-head/component.html +17 -0
- package/themes/newsflash/_components/heading/rule-head/component.json5 +23 -0
- package/themes/newsflash/_components/news/byline/component.html +12 -0
- package/themes/newsflash/_components/news/byline/component.json5 +18 -0
- package/themes/newsflash/_components/news/story-card/component.html +25 -0
- package/themes/newsflash/_components/news/story-card/component.json5 +21 -0
- package/themes/newsflash/_config.scss +147 -0
- package/themes/newsflash/_layouts/frontend/core/base.html +51 -0
- package/themes/newsflash/_layouts/frontend/pages/blog/index.html +299 -0
- package/themes/newsflash/_layouts/frontend/pages/blog/post.html +235 -0
- package/themes/newsflash/_layouts/frontend/pages/index.html +328 -0
- package/themes/newsflash/_sections/marketing/desks/section.html +39 -0
- package/themes/newsflash/_sections/marketing/desks/section.json5 +49 -0
- package/themes/newsflash/_sections/marketing/newsletter-cta/section.html +103 -0
- package/themes/newsflash/_sections/marketing/newsletter-cta/section.json5 +50 -0
- package/themes/newsflash/_sections/marketing/rundown/section.html +28 -0
- package/themes/newsflash/_sections/marketing/rundown/section.json5 +40 -0
- package/themes/newsflash/_theme.js +24 -0
- package/themes/newsflash/_theme.scss +70 -0
- package/themes/newsflash/css/base/_fonts.scss +80 -0
- package/themes/newsflash/css/base/_mixins.scss +51 -0
- package/themes/newsflash/css/base/_root.scss +257 -0
- package/themes/newsflash/css/base/_typography.scss +49 -0
- package/themes/newsflash/css/base/_utilities.scss +122 -0
- package/themes/newsflash/css/components/_badges.scss +65 -0
- package/themes/newsflash/css/components/_billing-toggle.scss +61 -0
- package/themes/newsflash/css/components/_buttons.scss +139 -0
- package/themes/newsflash/css/components/_cards.scss +47 -0
- package/themes/newsflash/css/components/_editorial.scss +173 -0
- package/themes/newsflash/css/components/_forms.scss +75 -0
- package/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
- package/themes/newsflash/css/components/_panels.scss +156 -0
- package/themes/newsflash/css/components/_ticker.scss +67 -0
- package/themes/newsflash/css/layout/_general.scss +330 -0
- package/themes/newsflash/css/layout/_navigation.scss +168 -0
- package/themes/newsflash/css/pages/404/index.scss +79 -0
- package/themes/newsflash/css/pages/blog/[slug].scss +186 -0
- package/themes/newsflash/css/pages/blog/index.scss +17 -0
- package/themes/newsflash/css/pages/index.scss +157 -0
- package/themes/newsflash/css/pages/pricing/index.scss +55 -0
- package/themes/newsflash/css/pages/test/libraries/layers/index.scss +7 -0
- package/themes/newsflash/fonts/fraunces-italic-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-italic-latin.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-normal-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-normal-latin.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-italic-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-italic-latin.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-normal-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-normal-latin.woff2 +0 -0
- package/themes/newsflash/js/pages/blog/[slug].js +29 -0
- package/themes/newsflash/js/pages/test/libraries/layers/index.js +9 -0
- package/translations/es/pages/404.json +86 -0
- package/translations/es/pages/account.json +36 -0
- package/translations/es/pages/app.json +44 -0
- package/translations/es/pages/cancel.json +36 -0
- package/translations/es/pages/change-password.json +36 -0
- package/translations/es/pages/connections/callback.json +41 -0
- package/translations/es/pages/dashboard/account.json +475 -0
- package/translations/es/pages/dashboard.json +36 -0
- package/translations/es/pages/forgot.json +36 -0
- package/translations/es/pages/join.json +36 -0
- package/translations/es/pages/login.json +36 -0
- package/translations/es/pages/payment/checkout.json +83 -0
- package/translations/es/pages/payment/confirmation.json +62 -0
- package/translations/es/pages/portal/email-preferences.json +46 -0
- package/translations/es/pages/recover.json +36 -0
- package/translations/es/pages/refund.json +36 -0
- package/translations/es/pages/register.json +36 -0
- package/translations/es/pages/reset-password.json +36 -0
- package/translations/es/pages/reset.json +42 -0
- package/translations/es/pages/signin.json +45 -0
- package/translations/es/pages/signup.json +60 -0
- package/translations/es/pages/token.json +39 -0
- package/translations/fa/pages/404.json +86 -0
- package/translations/fa/pages/account.json +36 -0
- package/translations/fa/pages/app.json +44 -0
- package/translations/fa/pages/cancel.json +36 -0
- package/translations/fa/pages/change-password.json +36 -0
- package/translations/fa/pages/connections/callback.json +41 -0
- package/translations/fa/pages/dashboard/account.json +475 -0
- package/translations/fa/pages/dashboard.json +36 -0
- package/translations/fa/pages/forgot.json +36 -0
- package/translations/fa/pages/join.json +36 -0
- package/translations/fa/pages/login.json +36 -0
- package/translations/fa/pages/payment/checkout.json +83 -0
- package/translations/fa/pages/payment/confirmation.json +62 -0
- package/translations/fa/pages/portal/email-preferences.json +46 -0
- package/translations/fa/pages/recover.json +36 -0
- package/translations/fa/pages/refund.json +36 -0
- package/translations/fa/pages/register.json +36 -0
- package/translations/fa/pages/reset-password.json +36 -0
- package/translations/fa/pages/reset.json +42 -0
- package/translations/fa/pages/signin.json +45 -0
- package/translations/fa/pages/signup.json +60 -0
- package/translations/fa/pages/token.json +39 -0
|
@@ -0,0 +1,1929 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rules.js — the executable codemod rules for `omega migrate`.
|
|
3
|
+
*
|
|
4
|
+
* The 8 rule classes extracted from the A2 real-file ports (DECISION.md
|
|
5
|
+
* "consumer conversion plan"), plus the analytics-spelling normalization
|
|
6
|
+
* (rule 9, from the omega.json5 flip), the WM → @omega.js/client rename
|
|
7
|
+
* surface (rules 10–15, #248), the classy gradient utilities classy v2
|
|
8
|
+
* dropped (rule 16, #296) and the dead `asset_path` frontmatter key (rule 17,
|
|
9
|
+
* #470). Every rule is a pure text → text transform over
|
|
10
|
+
* ONE file: `apply(text, ctx)` returns `{ text, edits, findings }` where
|
|
11
|
+
* `edits` are applied rewrites and `findings` are lint-level observations the
|
|
12
|
+
* rule could NOT safely fix (surfaced by both `omega migrate` and
|
|
13
|
+
* `omega migrate --check`).
|
|
14
|
+
*
|
|
15
|
+
* TWO tables, by file surface: RULES over the src/** templates, JS_RULES over
|
|
16
|
+
* the consumer JS. Rules run in ORDER within each: page.resolved must collapse
|
|
17
|
+
* before the generic page.<key> rewrite, page.canonical.url before both, and
|
|
18
|
+
* the theme-prefixed include after the leading slash is gone.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Tags whose quoted args template-kit resolves as variables — the rule-3
|
|
22
|
+
// capture hoist is scoped to these (interpolation inside OTHER tags' quoted
|
|
23
|
+
// strings is a Jekyll silent no-op we only report).
|
|
24
|
+
const { TAGS, filters: { FILTER_NAMES } } = require('../vendor/template-kit/index.js');
|
|
25
|
+
|
|
26
|
+
// The config namespace (#607): a page's frontmatter overrides omega.json5
|
|
27
|
+
// under `config:`, and nothing that is not a config section may sit there.
|
|
28
|
+
const {
|
|
29
|
+
CONFIG_SECTIONS, RANDOM_ID_ASSIGN_IDIOM,
|
|
30
|
+
templateReads, randomIdReads, assignsRandomId,
|
|
31
|
+
} = require('../config-sections.js');
|
|
32
|
+
|
|
33
|
+
/** Does this bare frontmatter key belong under `config:`? */
|
|
34
|
+
const isMovableSection = (key) => CONFIG_SECTIONS.has(key);
|
|
35
|
+
|
|
36
|
+
// Packaged theme ids whose hardcoded layout prefixes the engine aliases
|
|
37
|
+
const PACKAGED_THEMES = ['classy', 'neobrutalism', 'newsflash', 'bootstrap'];
|
|
38
|
+
|
|
39
|
+
// Eleventy's real `page` properties — page.<key> references to these are
|
|
40
|
+
// valid and stay; everything else is Jekyll frontmatter-through-page.
|
|
41
|
+
const VALID_PAGE_PROPS = [
|
|
42
|
+
'url', 'date', 'fileSlug', 'filePathStem', 'inputPath', 'outputPath',
|
|
43
|
+
'outputFileExtension', 'templateSyntax', 'rawInput', 'lang',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// Jekyll page props with NO mechanical equivalent — flagged, never rewritten
|
|
47
|
+
const MANUAL_PAGE_PROPS = ['next', 'previous', 'collection'];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Split text into lines preserving the trailing-newline state.
|
|
51
|
+
* @param {string} text
|
|
52
|
+
* @returns {{ lines: string[], trailingNewline: boolean }}
|
|
53
|
+
*/
|
|
54
|
+
function toLines(text) {
|
|
55
|
+
const trailingNewline = text.endsWith('\n');
|
|
56
|
+
const lines = text.split('\n');
|
|
57
|
+
if (trailingNewline) lines.pop();
|
|
58
|
+
return { lines, trailingNewline };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Reassemble lines into text.
|
|
63
|
+
*/
|
|
64
|
+
function fromLines(lines, trailingNewline) {
|
|
65
|
+
return lines.join('\n') + (trailingNewline ? '\n' : '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Line-by-line regex replace helper: applies `pattern` → `replacement` per
|
|
70
|
+
* line and records one edit per changed line.
|
|
71
|
+
* @param {string} id - rule id for the edit records
|
|
72
|
+
* @param {string} text
|
|
73
|
+
* @param {RegExp} pattern - must carry the g flag
|
|
74
|
+
* @param {string|function} replacement
|
|
75
|
+
* @returns {{ text: string, edits: object[] }}
|
|
76
|
+
*/
|
|
77
|
+
function replacePerLine(id, text, pattern, replacement) {
|
|
78
|
+
const { lines, trailingNewline } = toLines(text);
|
|
79
|
+
const edits = [];
|
|
80
|
+
const out = lines.map((line, index) => {
|
|
81
|
+
const replaced = line.replace(pattern, replacement);
|
|
82
|
+
if (replaced !== line) edits.push({ rule: id, line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
83
|
+
return replaced;
|
|
84
|
+
});
|
|
85
|
+
return { text: fromLines(out, trailingNewline), edits };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Rule 0 — legacy `uj_`/`uj-`/`site.uj` spellings → the `omega` names
|
|
90
|
+
// (runs first: every later rule matches template-kit's CURRENT tag names)
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
// The context-coupled filters register-liquid installs directly, so they are
|
|
94
|
+
// absent from FILTER_NAMES but are still part of the renamed surface.
|
|
95
|
+
const CONTEXT_FILTERS = ['omega_liquify', 'omega_content_format', 'omega_increment_return'];
|
|
96
|
+
|
|
97
|
+
// `uj_<name>` for every name the kit registers today — a legacy template that
|
|
98
|
+
// spelled something else keeps its text (it was never a template-kit call).
|
|
99
|
+
const LEGACY_NAMES = [...Object.keys(TAGS), ...Object.keys(FILTER_NAMES), ...CONTEXT_FILTERS]
|
|
100
|
+
.map((name) => name.replace(/^omega_/, ''));
|
|
101
|
+
|
|
102
|
+
const LEGACY_CLASSES = {
|
|
103
|
+
'uj-password-show': 'omega-password-show',
|
|
104
|
+
'uj-password-hide': 'omega-password-hide',
|
|
105
|
+
'uj-language-flag': 'omega-language-flag',
|
|
106
|
+
'uj-language-dropdown': 'omega-language-dropdown',
|
|
107
|
+
'data-uj-no-translate': 'data-omega-no-translate',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const legacyPrefix = {
|
|
111
|
+
id: 'legacy-prefix',
|
|
112
|
+
title: '`uj_*` tags/filters → `omega_*`; `site.uj` → `site.omega`; `uj-*` classes → `omega-*`',
|
|
113
|
+
apply(text) {
|
|
114
|
+
const names = new RegExp(`\\buj_(${LEGACY_NAMES.join('|')})\\b`, 'g');
|
|
115
|
+
const classes = new RegExp(Object.keys(LEGACY_CLASSES).join('|'), 'g');
|
|
116
|
+
const { lines, trailingNewline } = toLines(text);
|
|
117
|
+
const edits = [];
|
|
118
|
+
const out = lines.map((line, index) => {
|
|
119
|
+
const replaced = line
|
|
120
|
+
.replace(names, 'omega_$1')
|
|
121
|
+
.replace(/\bsite\.uj\b/g, 'site.omega')
|
|
122
|
+
.replace(/\buj-schema-/g, 'omega-schema-')
|
|
123
|
+
.replace(classes, (whole) => LEGACY_CLASSES[whole]);
|
|
124
|
+
if (replaced !== line) edits.push({ rule: 'legacy-prefix', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
125
|
+
return replaced;
|
|
126
|
+
});
|
|
127
|
+
return { text: fromLines(out, trailingNewline), edits, findings: [] };
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Rule 27 — the icon TAG → native Font Awesome markup (#619)
|
|
133
|
+
//
|
|
134
|
+
// Icons are not a tag any more, on either side of the rename: authoring is
|
|
135
|
+
// `<i class="fa-solid fa-rocket">`, which the build inlines and the runtime
|
|
136
|
+
// watcher upgrades. So a legacy `{% uj_icon %}` (and an already-half-migrated
|
|
137
|
+
// `{% omega_icon %}`) converts to markup, never to a tag that no longer
|
|
138
|
+
// exists. Runs FIRST — before the uj_→omega_ rename and before the tag-arg
|
|
139
|
+
// hoist, both of which would otherwise treat it as a live tag.
|
|
140
|
+
//
|
|
141
|
+
// The name arg becomes the `fa-<name>` class (a Liquid variable interpolates
|
|
142
|
+
// into it), the positional class arg rides along verbatim, and the weight is
|
|
143
|
+
// always `fa-solid`: the icon lookup falls back to `brands/` for any weight,
|
|
144
|
+
// so a brand name resolves without the converter knowing the icon set.
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
const ICON_TAG_PATTERN = /\{%-?\s*(?:uj|omega)_icon\s*([^%]*?)-?%\}/g;
|
|
147
|
+
|
|
148
|
+
const iconTagMarkup = {
|
|
149
|
+
id: 'icon-tag-markup',
|
|
150
|
+
title: '`uj_icon`/`omega_icon` tags → native Font Awesome markup',
|
|
151
|
+
apply(text) {
|
|
152
|
+
const { lines, trailingNewline } = toLines(text);
|
|
153
|
+
const edits = [];
|
|
154
|
+
const out = lines.map((line, index) => {
|
|
155
|
+
const replaced = line.replace(ICON_TAG_PATTERN, (whole, markup) => {
|
|
156
|
+
const args = splitTagArgs(markup.trim());
|
|
157
|
+
if (!args.length) return whole;
|
|
158
|
+
|
|
159
|
+
const name = quotedValue(args[0]);
|
|
160
|
+
const nameClass = name === null ? `fa-{{ ${args[0]} }}` : `fa-${name}`;
|
|
161
|
+
// Only the POSITIONAL class arg carries over; a `label=` option has no
|
|
162
|
+
// native equivalent, so the icon converts and the option drops.
|
|
163
|
+
const extraArg = args[1] && !args[1].includes('=') ? args[1] : null;
|
|
164
|
+
const extraValue = extraArg === null ? '' : (quotedValue(extraArg) ?? `{{ ${extraArg} }}`);
|
|
165
|
+
const classes = extraValue ? `fa-solid ${nameClass} ${extraValue}` : `fa-solid ${nameClass}`;
|
|
166
|
+
|
|
167
|
+
return `<i class="${classes}"></i>`;
|
|
168
|
+
});
|
|
169
|
+
if (replaced !== line) edits.push({ rule: 'icon-tag-markup', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
170
|
+
return replaced;
|
|
171
|
+
});
|
|
172
|
+
return { text: fromLines(out, trailingNewline), edits, findings: [] };
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/** Split a tag's markup on top-level commas, respecting quotes. */
|
|
177
|
+
function splitTagArgs(markup) {
|
|
178
|
+
const args = [];
|
|
179
|
+
let current = '';
|
|
180
|
+
let quote = null;
|
|
181
|
+
for (const char of markup) {
|
|
182
|
+
if (quote) {
|
|
183
|
+
current += char;
|
|
184
|
+
if (char === quote) quote = null;
|
|
185
|
+
} else if (char === '"' || char === "'") {
|
|
186
|
+
quote = char;
|
|
187
|
+
current += char;
|
|
188
|
+
} else if (char === ',') {
|
|
189
|
+
args.push(current.trim());
|
|
190
|
+
current = '';
|
|
191
|
+
} else {
|
|
192
|
+
current += char;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (current.trim()) args.push(current.trim());
|
|
196
|
+
return args;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The inside of a quoted arg, or null when the arg is an expression. */
|
|
200
|
+
function quotedValue(arg) {
|
|
201
|
+
return /^(['"]).*\1$/.test(arg) ? arg.slice(1, -1).trim() : null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Rule 1 — page.resolved.* → resolved.*
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
const pageResolved = {
|
|
208
|
+
id: 'page-resolved',
|
|
209
|
+
title: '`page.resolved.` → `resolved.`',
|
|
210
|
+
apply(text) {
|
|
211
|
+
const { text: out, edits } = replacePerLine('page-resolved', text, /\bpage\.resolved\./g, 'resolved.');
|
|
212
|
+
return { text: out, edits, findings: [] };
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Rule 2 — bracket/hardcoded theme layout values → plain layout names
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
const bracketLayout = {
|
|
220
|
+
id: 'bracket-layout',
|
|
221
|
+
title: 'bracket theme layout values → plain layout names',
|
|
222
|
+
apply(text) {
|
|
223
|
+
const prefix = new RegExp(
|
|
224
|
+
`themes/(?:\\[\\s*site\\.theme\\.id\\s*\\]|${PACKAGED_THEMES.join('|')})/`,
|
|
225
|
+
'g'
|
|
226
|
+
);
|
|
227
|
+
const { lines, trailingNewline } = toLines(text);
|
|
228
|
+
const edits = [];
|
|
229
|
+
const out = lines.map((line, index) => {
|
|
230
|
+
if (!/^\s*layout:/.test(line)) return line;
|
|
231
|
+
const replaced = line.replace(prefix, '');
|
|
232
|
+
if (replaced !== line) edits.push({ rule: 'bracket-layout', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
233
|
+
return replaced;
|
|
234
|
+
});
|
|
235
|
+
return { text: fromLines(out, trailingNewline), edits, findings: [] };
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Rule 3 — {{ … }} inside quoted template-kit tag args → {% capture %} hoist
|
|
241
|
+
// (upstream Jekyll silently no-ops these — live somiibo ships the bug)
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
const TK_TAG_NAMES = Object.keys(TAGS);
|
|
244
|
+
|
|
245
|
+
const tagArgInterpolation = {
|
|
246
|
+
id: 'tag-arg-interpolation',
|
|
247
|
+
title: 'interpolated quoted tag args → {% capture %} hoist',
|
|
248
|
+
apply(text) {
|
|
249
|
+
const { lines, trailingNewline } = toLines(text);
|
|
250
|
+
const edits = [];
|
|
251
|
+
const findings = [];
|
|
252
|
+
const out = [];
|
|
253
|
+
let captureCount = 0;
|
|
254
|
+
|
|
255
|
+
const tagLine = new RegExp(`\\{%-?\\s*(${TK_TAG_NAMES.join('|')})\\b[^%]*%\\}`);
|
|
256
|
+
for (let i = 0; i < lines.length; i++) {
|
|
257
|
+
const line = lines[i];
|
|
258
|
+
const match = line.match(tagLine);
|
|
259
|
+
// Only interpolation INSIDE the tag span matters — {{ }} elsewhere on
|
|
260
|
+
// the line is ordinary output.
|
|
261
|
+
if (!match || !/\{\{[^}]*\}\}/.test(match[0])) { out.push(line); continue; }
|
|
262
|
+
|
|
263
|
+
// Rewrite quoted args containing {{ }} INSIDE the tag span only — a
|
|
264
|
+
// quoted interpolation elsewhere on the line (an HTML attribute) is
|
|
265
|
+
// ordinary output and must not be touched.
|
|
266
|
+
const indent = (line.match(/^\s*/) || [''])[0];
|
|
267
|
+
const captures = [];
|
|
268
|
+
const span = match[0];
|
|
269
|
+
const rewrittenSpan = span.replace(/(["'])((?:(?!\1).)*\{\{(?:(?!\1).)*)\1/g, (whole, quote, inner) => {
|
|
270
|
+
// Only hoist when the quoted string actually interpolates
|
|
271
|
+
if (!/\{\{.*\}\}/.test(inner)) return whole;
|
|
272
|
+
captureCount += 1;
|
|
273
|
+
const varName = `omega_migrate_arg_${captureCount}`;
|
|
274
|
+
captures.push(`${indent}{% capture ${varName} %}${inner}{% endcapture %}`);
|
|
275
|
+
return varName;
|
|
276
|
+
});
|
|
277
|
+
const rewritten = line.replace(span, rewrittenSpan);
|
|
278
|
+
|
|
279
|
+
if (captures.length === 0) {
|
|
280
|
+
// {{ }} on a tk-tag line but outside its quoted args — report only
|
|
281
|
+
findings.push({
|
|
282
|
+
check: 'tag-arg-interpolation', line: i + 1, severity: 'warning',
|
|
283
|
+
message: `\`{{ }}\` near {% ${match[1]} %} but not in a quoted arg — verify manually`,
|
|
284
|
+
});
|
|
285
|
+
out.push(line);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (const capture of captures) out.push(capture);
|
|
290
|
+
edits.push({ rule: 'tag-arg-interpolation', line: i + 1, before: line.trim(), after: rewritten.trim() });
|
|
291
|
+
out.push(rewritten);
|
|
292
|
+
}
|
|
293
|
+
return { text: fromLines(out, trailingNewline), edits, findings };
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Rule 4 — forloop.parentloop.* → hoisted {% assign %} after the parent for
|
|
299
|
+
// (LiquidJS has no parentloop — upstream these render empty)
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
const PARENTLOOP_PROPS = ['index', 'index0', 'first', 'last', 'length', 'rindex', 'rindex0'];
|
|
302
|
+
|
|
303
|
+
const LOOP_TOKEN = /\{%-?\s*(for\s+\w+\s+in|endfor|raw|endraw|comment|endcomment)\b/g;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Walk one line's loop-structure tokens in order, updating the open-for stack
|
|
307
|
+
* (line indexes) and the inert-span flag. Returns how many tokens touched the
|
|
308
|
+
* for stack (0 = the line is structure-neutral).
|
|
309
|
+
*/
|
|
310
|
+
function trackLoopTokens(line, lineIndex, forStack, state) {
|
|
311
|
+
let structural = 0;
|
|
312
|
+
for (const [, token] of line.matchAll(LOOP_TOKEN)) {
|
|
313
|
+
if (token === 'raw' || token === 'comment') { state.inert += 1; continue; }
|
|
314
|
+
if (token === 'endraw' || token === 'endcomment') { state.inert = Math.max(0, state.inert - 1); continue; }
|
|
315
|
+
if (state.inert > 0) continue;
|
|
316
|
+
if (token === 'endfor') { if (forStack.length) forStack.pop(); structural += 1; continue; }
|
|
317
|
+
forStack.push(lineIndex);
|
|
318
|
+
structural += 1;
|
|
319
|
+
}
|
|
320
|
+
return structural;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const parentloop = {
|
|
324
|
+
id: 'parentloop',
|
|
325
|
+
title: '`forloop.parentloop.*` → hoisted parent-loop {% assign %}',
|
|
326
|
+
apply(text) {
|
|
327
|
+
const { lines, trailingNewline } = toLines(text);
|
|
328
|
+
const edits = [];
|
|
329
|
+
const findings = [];
|
|
330
|
+
|
|
331
|
+
// Pass 1: locate for/endfor nesting (token-order aware — a line may open
|
|
332
|
+
// or close several loops) and decide which parent-for lines need which
|
|
333
|
+
// props hoisted. forStack holds line indexes of open {% for %}s.
|
|
334
|
+
const forStack = [];
|
|
335
|
+
const state = { inert: 0 };
|
|
336
|
+
const hoists = new Map(); // parent for-line index → Set of props
|
|
337
|
+
const lineRewrites = new Map(); // ref line index → depth of its parent for
|
|
338
|
+
|
|
339
|
+
const flag = (line, message) => findings.push({ check: 'parentloop', line, severity: 'error', message });
|
|
340
|
+
|
|
341
|
+
for (let i = 0; i < lines.length; i++) {
|
|
342
|
+
const line = lines[i];
|
|
343
|
+
const hasRef = state.inert === 0 && /forloop\.parentloop\./.test(line);
|
|
344
|
+
|
|
345
|
+
if (hasRef && /forloop\.parentloop\.parentloop/.test(line)) {
|
|
346
|
+
flag(i + 1, 'multi-level `forloop.parentloop.parentloop` — hoist manually');
|
|
347
|
+
} else if (hasRef && /\{%-?\s*(?:for\s+\w+\s+in|endfor)\b/.test(line)) {
|
|
348
|
+
// A ref sharing its line with for/endfor tokens is ambiguous — the
|
|
349
|
+
// stack state at the ref position is not the line-start state.
|
|
350
|
+
flag(i + 1, '`forloop.parentloop` on the same line as a loop open/close — hoist manually');
|
|
351
|
+
} else if (hasRef) {
|
|
352
|
+
if (forStack.length < 2) {
|
|
353
|
+
flag(i + 1, '`forloop.parentloop` outside a nested loop — remove or hoist manually');
|
|
354
|
+
} else if (forStack[forStack.length - 1] === forStack[forStack.length - 2]) {
|
|
355
|
+
// Parent and inner for share a line: an assign inserted after that
|
|
356
|
+
// line would land INSIDE the inner loop and read the wrong forloop.
|
|
357
|
+
flag(i + 1, 'parent and inner `{% for %}` share a line — hoist manually');
|
|
358
|
+
} else {
|
|
359
|
+
const parentForLine = forStack[forStack.length - 2];
|
|
360
|
+
const parentDepth = forStack.length - 1; // 1-based depth of the parent
|
|
361
|
+
const props = hoists.get(parentForLine) || new Set();
|
|
362
|
+
for (const [, prop] of line.matchAll(/forloop\.parentloop\.(\w+)/g)) {
|
|
363
|
+
if (PARENTLOOP_PROPS.includes(prop)) props.add(prop);
|
|
364
|
+
else flag(i + 1, `unknown parentloop property \`${prop}\` — hoist manually`);
|
|
365
|
+
}
|
|
366
|
+
hoists.set(parentForLine, props);
|
|
367
|
+
lineRewrites.set(i, parentDepth);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
trackLoopTokens(line, i, forStack, state);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (hoists.size === 0) return { text, edits, findings };
|
|
375
|
+
|
|
376
|
+
// Pass 2: emit — insert assigns right after each parent for-line (that
|
|
377
|
+
// line ends in the parent loop's scope: were an unclosed inner loop open
|
|
378
|
+
// there, pass 1 flagged the ref instead), and rewrite references with
|
|
379
|
+
// depth-scoped names (omega_parentloop<depth>_<prop>) so nested hoists
|
|
380
|
+
// never shadow each other.
|
|
381
|
+
const depthByFor = new Map();
|
|
382
|
+
{
|
|
383
|
+
const stack = [];
|
|
384
|
+
const rescanState = { inert: 0 };
|
|
385
|
+
for (let i = 0; i < lines.length; i++) {
|
|
386
|
+
trackLoopTokens(lines[i], i, stack, rescanState);
|
|
387
|
+
if (stack.length && stack[stack.length - 1] === i) depthByFor.set(i, stack.length);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const out = [];
|
|
392
|
+
for (let i = 0; i < lines.length; i++) {
|
|
393
|
+
let line = lines[i];
|
|
394
|
+
if (lineRewrites.has(i)) {
|
|
395
|
+
const depth = lineRewrites.get(i);
|
|
396
|
+
const rewritten = line.replace(/forloop\.parentloop\.(\w+)/g, (whole, prop) =>
|
|
397
|
+
PARENTLOOP_PROPS.includes(prop) ? `omega_parentloop${depth}_${prop}` : whole);
|
|
398
|
+
if (rewritten !== line) {
|
|
399
|
+
edits.push({ rule: 'parentloop', line: i + 1, before: line.trim(), after: rewritten.trim() });
|
|
400
|
+
line = rewritten;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
out.push(line);
|
|
404
|
+
if (hoists.has(i)) {
|
|
405
|
+
const indent = (lines[i].match(/^\s*/) || [''])[0];
|
|
406
|
+
const depth = depthByFor.get(i);
|
|
407
|
+
for (const prop of hoists.get(i)) {
|
|
408
|
+
out.push(`${indent} {% assign omega_parentloop${depth}_${prop} = forloop.${prop} %}`);
|
|
409
|
+
edits.push({ rule: 'parentloop', line: i + 1, before: '(parent loop)', after: `{% assign omega_parentloop${depth}_${prop} = forloop.${prop} %}` });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return { text: fromLines(out, trailingNewline), edits, findings };
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// Rule 5 — strip leading slash from include paths
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
const includeSlash = {
|
|
421
|
+
id: 'include-slash',
|
|
422
|
+
title: 'strip leading `/` from include paths',
|
|
423
|
+
apply(text) {
|
|
424
|
+
const { text: out, edits } = replacePerLine('include-slash', text, /(\{%-?\s*include(?:_cached)?\s+)\//g, '$1');
|
|
425
|
+
return { text: out, edits, findings: [] };
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// Rule 7 (before 6) — page.canonical.url → {{ site.url }}{{ page.url }}
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
const canonicalUrl = {
|
|
433
|
+
id: 'canonical-url',
|
|
434
|
+
title: '`page.canonical.url` → `{{ site.url }}{{ page.url }}`',
|
|
435
|
+
apply(text) {
|
|
436
|
+
const { text: replaced, edits } = replacePerLine(
|
|
437
|
+
'canonical-url', text,
|
|
438
|
+
/\{\{\s*page\.canonical\.url\s*\}\}/g,
|
|
439
|
+
'{{ site.url }}{{ page.url }}'
|
|
440
|
+
);
|
|
441
|
+
const findings = [];
|
|
442
|
+
const { lines } = toLines(replaced);
|
|
443
|
+
lines.forEach((line, index) => {
|
|
444
|
+
if (/page\.canonical/.test(line)) {
|
|
445
|
+
findings.push({
|
|
446
|
+
check: 'canonical-url', line: index + 1, severity: 'error',
|
|
447
|
+
message: '`page.canonical` outside a plain output — rewrite to `site.url`/`page.url` manually',
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
return { text: replaced, edits, findings };
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
// Rule 6 — page.<key> Jekyll frontmatter reads → top-level keys
|
|
457
|
+
//
|
|
458
|
+
// The rewrite is SCOPE-AWARE ([#541](https://github.com/Omega-JS-Stack/omega/issues/541)):
|
|
459
|
+
// inside a `{% for %}`/`{% tablerow %}` binding the same name, `<key>` is the
|
|
460
|
+
// LOOP ITEM, so the rename would silently repoint the read — `{% if tag !=
|
|
461
|
+
// page.tag.name %}` inside `{% for tag in … %}` becomes a comparison of the
|
|
462
|
+
// item with itself, an always-true exclude guard on a page that still builds.
|
|
463
|
+
// Those occurrences are skipped and reported; occurrences outside the loop
|
|
464
|
+
// still rewrite.
|
|
465
|
+
//
|
|
466
|
+
// Its own tracker, not rule 4's: that one answers "how deep, on which line"
|
|
467
|
+
// for the parentloop hoist and must NOT count `{% tablerow %}` (a tablerow
|
|
468
|
+
// exposes `tablerowloop`, never `forloop`); this one answers "which NAMES are
|
|
469
|
+
// bound here", where a tablerow shadows exactly like a for.
|
|
470
|
+
// ---------------------------------------------------------------------------
|
|
471
|
+
const SCOPE_TOKEN = /\{%-?\s*(?:for|tablerow)\s+(\w+)\s+in\b|\{%-?\s*(endfor|endtablerow|raw|endraw|comment|endcomment)\b/g;
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Walk one line's scope tokens in order, updating the bound-name stack and the
|
|
475
|
+
* inert-span flag.
|
|
476
|
+
* @returns {string[]} the names bound by loops OPENED on this line (in scope
|
|
477
|
+
* for the rest of it)
|
|
478
|
+
*/
|
|
479
|
+
function trackLoopScope(line, stack, state) {
|
|
480
|
+
const opened = [];
|
|
481
|
+
for (const [, name, keyword] of line.matchAll(SCOPE_TOKEN)) {
|
|
482
|
+
if (keyword === 'raw' || keyword === 'comment') { state.inert += 1; continue; }
|
|
483
|
+
if (keyword === 'endraw' || keyword === 'endcomment') { state.inert = Math.max(0, state.inert - 1); continue; }
|
|
484
|
+
if (state.inert > 0) continue;
|
|
485
|
+
if (keyword) { if (stack.length) stack.pop(); continue; }
|
|
486
|
+
stack.push(name);
|
|
487
|
+
opened.push(name);
|
|
488
|
+
}
|
|
489
|
+
return opened;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const pageProps = {
|
|
493
|
+
id: 'page-props',
|
|
494
|
+
title: '`page.<frontmatterKey>` → `<key>`; `page.content` → `content`; `page.slug` → `page.fileSlug`',
|
|
495
|
+
apply(text) {
|
|
496
|
+
const { lines, trailingNewline } = toLines(text);
|
|
497
|
+
const edits = [];
|
|
498
|
+
const findings = [];
|
|
499
|
+
const stack = [];
|
|
500
|
+
const state = { inert: 0 };
|
|
501
|
+
// The lookbehind keeps `page.` out of larger words and paths —
|
|
502
|
+
// `words-on-a-page.png`, `/landing-page.html`, `data.page.url` are NOT
|
|
503
|
+
// page-object references.
|
|
504
|
+
const out = lines.map((line, index) => {
|
|
505
|
+
const bound = new Set(stack);
|
|
506
|
+
for (const name of trackLoopScope(line, stack, state)) bound.add(name);
|
|
507
|
+
let replaced = line
|
|
508
|
+
.replace(/(?<![\w./-])page\.content\b/g, (whole) => (bound.has('content') ? whole : 'content'))
|
|
509
|
+
.replace(/(?<![\w./-])page\.slug\b/g, 'page.fileSlug');
|
|
510
|
+
replaced = replaced.replace(/(?<![\w./-])page\.([A-Za-z_][\w]*)\b/g, (whole, key) => {
|
|
511
|
+
if (VALID_PAGE_PROPS.includes(key)) return whole;
|
|
512
|
+
if (MANUAL_PAGE_PROPS.includes(key)) {
|
|
513
|
+
findings.push({
|
|
514
|
+
check: 'page-props', line: index + 1, severity: 'error',
|
|
515
|
+
message: `\`page.${key}\` has no mechanical equivalent — port manually (collections API)`,
|
|
516
|
+
});
|
|
517
|
+
return whole;
|
|
518
|
+
}
|
|
519
|
+
if (bound.has(key)) {
|
|
520
|
+
findings.push({
|
|
521
|
+
check: 'page-props', line: index + 1, severity: 'error',
|
|
522
|
+
message: `\`page.${key}\` left alone: an enclosing \`{% for ${key} in … %}\`/\`{% tablerow %}\` binds \`${key}\`, `
|
|
523
|
+
+ `so the rename would read the LOOP ITEM instead of the page's \`${key}\` — hoist the page value before the loop `
|
|
524
|
+
+ `(\`{% assign page_${key} = ${key} %}\` above it) or rename the loop variable, by hand`,
|
|
525
|
+
});
|
|
526
|
+
return whole;
|
|
527
|
+
}
|
|
528
|
+
return key;
|
|
529
|
+
});
|
|
530
|
+
if (replaced !== line) edits.push({ rule: 'page-props', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
531
|
+
return replaced;
|
|
532
|
+
});
|
|
533
|
+
return { text: fromLines(out, trailingNewline), edits, findings };
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// ---------------------------------------------------------------------------
|
|
538
|
+
// Rule 9 — flat analytics spellings → providers shape
|
|
539
|
+
// ---------------------------------------------------------------------------
|
|
540
|
+
const analyticsShape = {
|
|
541
|
+
id: 'analytics-shape',
|
|
542
|
+
title: '`site.analytics.<provider>` → `site.analytics.providers.<provider>.id`',
|
|
543
|
+
apply(text) {
|
|
544
|
+
const { text: out, edits } = replacePerLine(
|
|
545
|
+
'analytics-shape', text,
|
|
546
|
+
/\b(site|resolved)\.analytics\.(google|meta|tiktok)\b(?!\.)/g,
|
|
547
|
+
'$1.analytics.providers.$2.id'
|
|
548
|
+
);
|
|
549
|
+
return { text: out, edits, findings: [] };
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// ---------------------------------------------------------------------------
|
|
554
|
+
// Rule 24 — config READS → `resolved.config.<section>`
|
|
555
|
+
// ([#611](https://github.com/Omega-JS-Stack/omega/issues/611)). #607 moved the
|
|
556
|
+
// brand config off `site.*` onto `resolved.config.*`, and the old spellings do
|
|
557
|
+
// not fail: `{{ site.brand.name }}` renders an EMPTY STRING, on every page that
|
|
558
|
+
// carries one. Rule 23 moves the frontmatter KEYS a page restates; this one
|
|
559
|
+
// moves the reads, in bodies and frontmatter values alike.
|
|
560
|
+
//
|
|
561
|
+
// TWO dead spellings, because the port produces both: the `site.<section>` a
|
|
562
|
+
// UJM page wrote by hand, and the flat `resolved.<section>` rule 1 manufactures
|
|
563
|
+
// out of `page.resolved.<section>`. `site.meta` is the meta WALK rather than a
|
|
564
|
+
// config section, so it lands on `resolved.meta`, where the walk lives.
|
|
565
|
+
//
|
|
566
|
+
// Runs AFTER rule 9: `site.analytics.<provider>` is that rule's input, and
|
|
567
|
+
// renaming the root first would strand the flat spelling it answers to.
|
|
568
|
+
// ---------------------------------------------------------------------------
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* What one read becomes, or null when it is not this rule's business.
|
|
572
|
+
* @param {{ root: string, key: string }} read
|
|
573
|
+
* @returns {string|null} the replacement ROOT+KEY (the dotted tail rides along)
|
|
574
|
+
*/
|
|
575
|
+
function movedRead(read) {
|
|
576
|
+
// `meta` is not config at all — it is the page's own walk — so `site.meta`
|
|
577
|
+
// lands on `resolved.meta`, never under `config`.
|
|
578
|
+
if (read.key === 'meta') return read.root === 'site' ? 'resolved.meta' : null;
|
|
579
|
+
return CONFIG_SECTIONS.has(read.key) ? `resolved.config.${read.key}` : null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
// The SELF-REFERENCE (#671, part 2)
|
|
584
|
+
//
|
|
585
|
+
// A UJM page wrote `meta: { title: "{{ site.meta.title }}" }` to mean "use the
|
|
586
|
+
// site default". Rewritten it becomes `{{ resolved.meta.title }}` — and THIS
|
|
587
|
+
// block is what the meta walk resolves that from, so the page's <title> shipped
|
|
588
|
+
// the literal string `{{ resolved.meta.title }}`. There is nothing to rewrite
|
|
589
|
+
// it to: absence already falls through to the config default, so the read is
|
|
590
|
+
// DROPPED, key and all, with a finding naming the file. Both spellings go —
|
|
591
|
+
// a brand migrated before 0.46.0 is carrying the rewritten shape, and re-running
|
|
592
|
+
// migrate has to repair it rather than call the page done.
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Split a YAML flow mapping's body on its TOP-LEVEL commas (quotes and nested
|
|
597
|
+
* brackets hold their commas).
|
|
598
|
+
* @param {string} body - the text between `{` and `}`
|
|
599
|
+
* @returns {string[]}
|
|
600
|
+
*/
|
|
601
|
+
function splitFlowPairs(body) {
|
|
602
|
+
const pairs = [];
|
|
603
|
+
let depth = 0;
|
|
604
|
+
let quote = null;
|
|
605
|
+
let start = 0;
|
|
606
|
+
for (let i = 0; i < body.length; i++) {
|
|
607
|
+
const char = body[i];
|
|
608
|
+
if (quote) {
|
|
609
|
+
if (char === quote) quote = null;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (char === '"' || char === "'") quote = char;
|
|
613
|
+
else if (char === '{' || char === '[') depth += 1;
|
|
614
|
+
else if (char === '}' || char === ']') depth -= 1;
|
|
615
|
+
else if (char === ',' && depth === 0) {
|
|
616
|
+
pairs.push(body.slice(start, i));
|
|
617
|
+
start = i + 1;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
pairs.push(body.slice(start));
|
|
621
|
+
return pairs.filter((pair) => pair.trim() !== '');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* The `}` closing the flow mapping opened on `lines[start]`, quote-aware — a
|
|
626
|
+
* brace inside a quoted scalar is text, not structure.
|
|
627
|
+
* @param {string[]} lines
|
|
628
|
+
* @param {number} start - index of the line carrying the opening brace
|
|
629
|
+
* @param {number} limit - one past the last line the mapping may reach
|
|
630
|
+
* @returns {{ line: number, column: number }|null} null when it never closes
|
|
631
|
+
*/
|
|
632
|
+
function flowMappingEnd(lines, start, limit) {
|
|
633
|
+
let depth = 0;
|
|
634
|
+
let quote = null;
|
|
635
|
+
for (let i = start; i < limit; i++) {
|
|
636
|
+
const line = lines[i];
|
|
637
|
+
for (let c = 0; c < line.length; c++) {
|
|
638
|
+
const char = line[c];
|
|
639
|
+
if (quote) {
|
|
640
|
+
if (char === quote) quote = null;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (char === '"' || char === "'") quote = char;
|
|
644
|
+
else if (char === '{') depth += 1;
|
|
645
|
+
else if (char === '}') {
|
|
646
|
+
depth -= 1;
|
|
647
|
+
if (depth === 0) return { line: i, column: c };
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
quote = null; // a quoted YAML scalar does not span lines
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* The lines nested under the key on `lines[opener]` — everything more indented,
|
|
657
|
+
* blanks skipped, up to the first line that dedents.
|
|
658
|
+
* @param {string[]} lines
|
|
659
|
+
* @param {number} opener
|
|
660
|
+
* @param {number} limit - one past the last line to consider (the fence)
|
|
661
|
+
* @returns {number[]} line indexes
|
|
662
|
+
*/
|
|
663
|
+
function nestedLines(lines, opener, limit) {
|
|
664
|
+
const indent = lines[opener].match(/^[ \t]*/)[0].length;
|
|
665
|
+
const children = [];
|
|
666
|
+
for (let i = opener + 1; i < limit; i++) {
|
|
667
|
+
if (lines[i].trim() === '') continue;
|
|
668
|
+
if (lines[i].match(/^[ \t]*/)[0].length <= indent) break;
|
|
669
|
+
children.push(i);
|
|
670
|
+
}
|
|
671
|
+
return children;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Drop the reads of the meta walk that sit inside the page's OWN `meta:` block.
|
|
676
|
+
* @param {string} text
|
|
677
|
+
* @returns {{ text: string, edits: object[], findings: object[] }}
|
|
678
|
+
*/
|
|
679
|
+
function dropSelfMetaReads(text) {
|
|
680
|
+
const { lines, trailingNewline } = toLines(text);
|
|
681
|
+
const sourceLines = [...lines];
|
|
682
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
683
|
+
if (fenceEnd < 0) return { text, edits: [], findings: [] };
|
|
684
|
+
|
|
685
|
+
const readLines = new Set(
|
|
686
|
+
templateReads(text).filter((read) => read.key === 'meta' && read.line <= fenceEnd).map((read) => read.line),
|
|
687
|
+
);
|
|
688
|
+
if (!readLines.size) return { text, edits: [], findings: [] };
|
|
689
|
+
|
|
690
|
+
const drop = new Set();
|
|
691
|
+
const rewritten = new Set();
|
|
692
|
+
const findings = [];
|
|
693
|
+
const say = (line, key, source) => findings.push({
|
|
694
|
+
check: 'config-reads', line, severity: 'warning',
|
|
695
|
+
message: `dropped \`${key}\` from this page's own \`meta:\` block (#671) — it read \`${source}\`, which IS the walk this block feeds, `
|
|
696
|
+
+ 'so the page rendered the literal expression. Absence falls through to the config default',
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
let block = null; // the `meta:` block we are inside: { opener, indent, children }
|
|
700
|
+
const blocks = [];
|
|
701
|
+
for (let i = 1; i < fenceEnd; i++) {
|
|
702
|
+
const line = lines[i];
|
|
703
|
+
const blank = line.trim() === '';
|
|
704
|
+
const indent = blank ? Infinity : line.match(/^[ \t]*/)[0].length;
|
|
705
|
+
if (block && !blank && indent <= block.indent) block = null;
|
|
706
|
+
|
|
707
|
+
if (block) {
|
|
708
|
+
if (!blank) block.children.push(i);
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const opener = line.match(/^([ \t]*)meta:\s*$/);
|
|
713
|
+
if (opener) {
|
|
714
|
+
block = { opener: i, indent: opener[1].length, children: [] };
|
|
715
|
+
blocks.push(block);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// The flow spelling, on ONE line (`meta: { title: "{{ site.meta.title }}" }`)
|
|
720
|
+
// or spread over several with the closing brace below — both are legal
|
|
721
|
+
// YAML, and a self-reference hides in either. Judged pair by pair, per
|
|
722
|
+
// line, exactly like the block form.
|
|
723
|
+
const flow = line.match(/^([ \t]*)meta:\s*\{/);
|
|
724
|
+
if (!flow) continue;
|
|
725
|
+
const close = flowMappingEnd(lines, i, fenceEnd);
|
|
726
|
+
if (!close) continue;
|
|
727
|
+
|
|
728
|
+
const open = line.indexOf('{', flow[1].length);
|
|
729
|
+
const segments = [];
|
|
730
|
+
for (let j = i; j <= close.line; j++) {
|
|
731
|
+
const body = lines[j].slice(j === i ? open + 1 : 0, j === close.line ? close.column : lines[j].length);
|
|
732
|
+
const pairs = splitFlowPairs(body);
|
|
733
|
+
segments.push({
|
|
734
|
+
index: j,
|
|
735
|
+
kept: !readLines.has(j + 1) ? pairs : pairs.filter((pair) => {
|
|
736
|
+
if (!/\{\{[^}]*\b(site|resolved)\.meta\b/.test(pair)) return true;
|
|
737
|
+
say(j + 1, (pair.match(/^\s*([\w-]+)\s*:/) || [null, 'meta'])[1], pair.trim());
|
|
738
|
+
return false;
|
|
739
|
+
}),
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (segments.every((segment) => segment.kept.length === 0)) {
|
|
744
|
+
for (let j = i; j <= close.line; j++) drop.add(j);
|
|
745
|
+
} else {
|
|
746
|
+
const last = segments.filter((segment) => segment.kept.length > 0).pop();
|
|
747
|
+
for (const segment of segments) {
|
|
748
|
+
const j = segment.index;
|
|
749
|
+
const head = j === i ? lines[j].slice(0, open + 1) : '';
|
|
750
|
+
const tail = j === close.line ? lines[j].slice(close.column) : '';
|
|
751
|
+
if (segment.kept.length === 0) {
|
|
752
|
+
// A line that carried nothing else goes; the opener and the closer
|
|
753
|
+
// stay to carry their braces.
|
|
754
|
+
if (!head && !tail) drop.add(j);
|
|
755
|
+
else lines[j] = `${head}${tail ? lines[j].match(/^[ \t]*/)[0] : ''}${tail}`;
|
|
756
|
+
} else {
|
|
757
|
+
const body = segment.kept.join(',') + (segment === last ? '' : ',');
|
|
758
|
+
lines[j] = `${head}${body}${tail}`;
|
|
759
|
+
}
|
|
760
|
+
if (lines[j] !== sourceLines[j] && !drop.has(j)) rewritten.add(j);
|
|
761
|
+
}
|
|
762
|
+
// The one-line form reads as one pair list — collapse the space the
|
|
763
|
+
// dropped pairs left before the closing brace.
|
|
764
|
+
if (i === close.line) lines[i] = lines[i].replace(/\s+\}(\s*)$/, ' }$1');
|
|
765
|
+
}
|
|
766
|
+
i = close.line;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
for (const entry of blocks) {
|
|
770
|
+
for (const child of entry.children.filter((line) => readLines.has(line + 1))) {
|
|
771
|
+
drop.add(child);
|
|
772
|
+
say(child + 1, (lines[child].match(/^\s*([\w-]+)\s*:/) || [null, 'meta'])[1], lines[child].trim());
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// An emptied key is a NULL key, not an absent one — and this engine's
|
|
777
|
+
// deepMerge lets a null REPLACE the config defaults it was supposed to fall
|
|
778
|
+
// through to. So every key the drop emptied goes with it, from the deepest
|
|
779
|
+
// nested one (an `og:` whose only child was a self-read) up to the `meta:`
|
|
780
|
+
// opener itself — a fixed point, because dropping one empties its parent.
|
|
781
|
+
for (let changed = true; changed;) {
|
|
782
|
+
changed = false;
|
|
783
|
+
for (let i = 1; i < fenceEnd; i++) {
|
|
784
|
+
if (drop.has(i) || !/^[ \t]*[\w-]+:\s*$/.test(lines[i])) continue;
|
|
785
|
+
const children = nestedLines(lines, i, fenceEnd);
|
|
786
|
+
if (children.length === 0 || !children.every((child) => drop.has(child))) continue;
|
|
787
|
+
drop.add(i);
|
|
788
|
+
changed = true;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (!drop.size && !rewritten.size) return { text, edits: [], findings: [] };
|
|
793
|
+
|
|
794
|
+
// One edit per changed line: a drop the report never counted was a repair
|
|
795
|
+
// the codemod never wrote (the write and the file listing both gate on edits).
|
|
796
|
+
const edits = [...new Set([...drop, ...rewritten])].sort((a, b) => a - b).map((index) => ({
|
|
797
|
+
rule: 'config-reads',
|
|
798
|
+
line: index + 1,
|
|
799
|
+
before: sourceLines[index].trim(),
|
|
800
|
+
after: drop.has(index) ? '(removed — a page may not read the walk its own `meta:` block feeds)' : lines[index].trim(),
|
|
801
|
+
}));
|
|
802
|
+
|
|
803
|
+
return { text: fromLines(lines.filter((line, index) => !drop.has(index)), trailingNewline), edits, findings };
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const configReads = {
|
|
807
|
+
id: 'config-reads',
|
|
808
|
+
title: '`site.<section>` / `resolved.<section>` reads → `resolved.config.<section>`',
|
|
809
|
+
apply(text) {
|
|
810
|
+
// A page's own `meta:` block first (#671): those reads are dropped, not
|
|
811
|
+
// moved, and dropping before the rewrite catches BOTH spellings on one path.
|
|
812
|
+
const self = dropSelfMetaReads(text);
|
|
813
|
+
|
|
814
|
+
// The census is config-sections.js's, shared with the engine's build guard
|
|
815
|
+
// (#611): only LIQUID context counts, and fenced code blocks and `{% raw %}`
|
|
816
|
+
// bodies are display, not markup (#521). Without that this rule turned
|
|
817
|
+
// `https://site.company.com/x` into `resolved.config.company.com` and
|
|
818
|
+
// rewrote the legacy spelling inside a page's own documentation of it —
|
|
819
|
+
// both found by the blind-verifier walk (2026-08-25).
|
|
820
|
+
//
|
|
821
|
+
// Splice from the END so every earlier offset stays valid.
|
|
822
|
+
const reads = templateReads(self.text).filter((read) => movedRead(read) !== null);
|
|
823
|
+
if (!reads.length) {
|
|
824
|
+
// Already migrated — idempotent (the self-reference drop may still have
|
|
825
|
+
// repaired a page rewritten by an older migrate, and THAT is an edit:
|
|
826
|
+
// the codemod writes and lists what the rules report as changed).
|
|
827
|
+
return { text: self.text, edits: self.edits, findings: self.findings };
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
let out = self.text;
|
|
831
|
+
const lines = new Map(); // line → { before, after } — one edit per changed line
|
|
832
|
+
for (const read of [...reads].reverse()) {
|
|
833
|
+
const tail = read.expression.slice(`${read.root}.${read.key}`.length);
|
|
834
|
+
out = out.slice(0, read.index) + movedRead(read) + tail + out.slice(read.index + read.expression.length);
|
|
835
|
+
}
|
|
836
|
+
const beforeLines = toLines(self.text).lines;
|
|
837
|
+
const afterLines = toLines(out).lines;
|
|
838
|
+
for (const read of reads) {
|
|
839
|
+
if (lines.has(read.line)) continue;
|
|
840
|
+
lines.set(read.line, { before: beforeLines[read.line - 1], after: afterLines[read.line - 1] });
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const moved = [...lines].map(([line, { before, after }]) => ({
|
|
844
|
+
rule: 'config-reads', line, before: before.trim(), after: after.trim(),
|
|
845
|
+
}));
|
|
846
|
+
|
|
847
|
+
return {
|
|
848
|
+
text: out,
|
|
849
|
+
edits: [...self.edits, ...moved],
|
|
850
|
+
findings: [...self.findings, {
|
|
851
|
+
check: 'config-reads', line: moved[0].line, severity: 'warning',
|
|
852
|
+
message: 'rewrote config reads to `resolved.config.<section>` (#607) — the brand config left the `site` global, '
|
|
853
|
+
+ 'where they rendered an empty string with no error. Check the page still reads what it meant',
|
|
854
|
+
}],
|
|
855
|
+
};
|
|
856
|
+
},
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// Rule 8 (Jekyll defaults/collections → targets.web) lives in config-convert.js
|
|
860
|
+
// — it moves config, not template text.
|
|
861
|
+
|
|
862
|
+
// ---------------------------------------------------------------------------
|
|
863
|
+
// Rules 10–15 — the WM → @omega.js/client rename surface
|
|
864
|
+
// ([#248](https://github.com/Omega-JS-Stack/omega/issues/248))
|
|
865
|
+
//
|
|
866
|
+
// Every UJM consumer carries it (~40 sites in one brand). The markup pair is
|
|
867
|
+
// the dangerous half: nothing fails at BUILD time, the bindings and the
|
|
868
|
+
// sign-out button simply go dead at runtime. The markup rule therefore runs
|
|
869
|
+
// over templates AND consumer JS (a querySelector spells the same class); the
|
|
870
|
+
// import rules are JS-only (JS_RULES) and the frontmatter/include rules
|
|
871
|
+
// template-only.
|
|
872
|
+
// ---------------------------------------------------------------------------
|
|
873
|
+
|
|
874
|
+
// Rule 10 — the client-runtime markup hooks, renamed with the runtime (#16)
|
|
875
|
+
const CLIENT_MARKUP = {
|
|
876
|
+
'data-wm-bind': 'data-omega-bind',
|
|
877
|
+
'auth-signout-btn': 'omega-signout',
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
const clientMarkup = {
|
|
881
|
+
id: 'client-markup',
|
|
882
|
+
title: '`data-wm-bind` → `data-omega-bind`; `auth-signout-btn` → `omega-signout`',
|
|
883
|
+
apply(text) {
|
|
884
|
+
// Both ends are guarded against `-` and word chars: a class list and an
|
|
885
|
+
// attribute name are hyphenated namespaces, so a bare alternation would
|
|
886
|
+
// eat `js-auth-signout-btn` and `auth-signout-btn-large` — consumer names
|
|
887
|
+
// that are DIFFERENT hooks and must survive untouched.
|
|
888
|
+
const pattern = new RegExp(`(?<![\\w-])(?:${Object.keys(CLIENT_MARKUP).join('|')})(?![\\w-])`, 'g');
|
|
889
|
+
const { text: out, edits } = replacePerLine('client-markup', text, pattern, (whole) => CLIENT_MARKUP[whole]);
|
|
890
|
+
return { text: out, edits, findings: [] };
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// ---------------------------------------------------------------------------
|
|
895
|
+
// Rule 11 — the client block's frontmatter key names: `web_manager:` →
|
|
896
|
+
// `client:` (#1: it configures @omega.js/client, and there is no dual-read) and
|
|
897
|
+
// its `cookieConsent:` sub-key → `consent:` (#383: the banner became a real
|
|
898
|
+
// consent gate, and a page that disabled the old name would silently stop
|
|
899
|
+
// disabling anything)
|
|
900
|
+
// ---------------------------------------------------------------------------
|
|
901
|
+
const clientFrontmatter = {
|
|
902
|
+
id: 'client-frontmatter',
|
|
903
|
+
title: 'frontmatter `web_manager:` → `client:`, `cookieConsent:` → `consent:`',
|
|
904
|
+
apply(text) {
|
|
905
|
+
const { lines, trailingNewline } = toLines(text);
|
|
906
|
+
// Scoped to the leading `---` fence: a `web_manager:` in the body is prose
|
|
907
|
+
// or data, never the client-config key.
|
|
908
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
909
|
+
if (fenceEnd < 0) return { text, edits: [], findings: [] };
|
|
910
|
+
|
|
911
|
+
const edits = [];
|
|
912
|
+
const out = lines.map((line, index) => {
|
|
913
|
+
if (index === 0 || index >= fenceEnd) return line;
|
|
914
|
+
// `cookieConsent` is always INDENTED here — it only ever existed as a
|
|
915
|
+
// sub-key of the client block, never at the frontmatter's top level.
|
|
916
|
+
const replaced = line
|
|
917
|
+
.replace(/^web_manager:/, 'client:')
|
|
918
|
+
.replace(/^(\s+)cookieConsent:/, '$1consent:');
|
|
919
|
+
if (replaced !== line) edits.push({ rule: 'client-frontmatter', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
920
|
+
return replaced;
|
|
921
|
+
});
|
|
922
|
+
return { text: fromLines(out, trailingNewline), edits, findings: [] };
|
|
923
|
+
},
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
// ---------------------------------------------------------------------------
|
|
927
|
+
// Rule 12 — theme-prefixed include paths → the plain layered path
|
|
928
|
+
// (runs AFTER include-slash, which owns the leading `/`)
|
|
929
|
+
// ---------------------------------------------------------------------------
|
|
930
|
+
const includeThemePath = {
|
|
931
|
+
id: 'include-theme-path',
|
|
932
|
+
title: '`{% include themes/<id>/… %}` → the layered include path',
|
|
933
|
+
apply(text) {
|
|
934
|
+
const pattern = new RegExp(
|
|
935
|
+
`(\\{%-?\\s*include(?:_cached)?\\s+)themes/(?:\\[\\s*site\\.theme\\.id\\s*\\]|${PACKAGED_THEMES.join('|')})/`,
|
|
936
|
+
'g'
|
|
937
|
+
);
|
|
938
|
+
const { text: out, edits } = replacePerLine('include-theme-path', text, pattern, '$1');
|
|
939
|
+
return { text: out, edits, findings: [] };
|
|
940
|
+
},
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// ---------------------------------------------------------------------------
|
|
944
|
+
// Rule 13 — the web-manager package + its singleton identifier
|
|
945
|
+
// (WebManager is not an OMEGA concept: the package is @omega.js/client and the
|
|
946
|
+
// singleton is `omega`, subpath modules included)
|
|
947
|
+
// ---------------------------------------------------------------------------
|
|
948
|
+
const clientImport = {
|
|
949
|
+
id: 'client-import',
|
|
950
|
+
title: "`'web-manager'` → `'@omega.js/client'`; the `webManager` singleton → `omega`",
|
|
951
|
+
apply(text) {
|
|
952
|
+
const { lines, trailingNewline } = toLines(text);
|
|
953
|
+
const edits = [];
|
|
954
|
+
const out = lines.map((line, index) => {
|
|
955
|
+
const replaced = line
|
|
956
|
+
.replace(/(["'])web-manager(\/[^"']*)?\1/g, (whole, quote, subpath) => `${quote}@omega.js/client${subpath || ''}${quote}`)
|
|
957
|
+
.replace(/\bwebManager\b/g, 'omega');
|
|
958
|
+
if (replaced !== line) edits.push({ rule: 'client-import', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
959
|
+
return replaced;
|
|
960
|
+
});
|
|
961
|
+
return { text: fromLines(out, trailingNewline), edits, findings: [] };
|
|
962
|
+
},
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// ---------------------------------------------------------------------------
|
|
966
|
+
// Rule 14 — the `__main_assets__/js/libs/*` runtime libs
|
|
967
|
+
//
|
|
968
|
+
// form-manager MOVED (same class, new home). authorized-fetch did not: its
|
|
969
|
+
// successor is the singleton's own `omega.request()`, so the import line goes
|
|
970
|
+
// and the calls are renamed — but the OPTIONS are the client's, not
|
|
971
|
+
// wonderful-fetch's, so every rewritten call is reported for review.
|
|
972
|
+
//
|
|
973
|
+
// The rename alone shipped two dead shapes with no error at all
|
|
974
|
+
// ([#594](https://github.com/Omega-JS-Stack/omega/issues/594)), so the rewrite
|
|
975
|
+
// carries them too:
|
|
976
|
+
// - `response: 'json'` — @omega.js/client parses by content type and RETURNS
|
|
977
|
+
// the body, so the option means nothing. Dropped from the rewritten call.
|
|
978
|
+
// - `err.status` — the HTTP status lives on `error.code`
|
|
979
|
+
// (packages/client/src/modules/request.js), so `if (err.status === 429)`
|
|
980
|
+
// compared `undefined` and the whole rate-limit branch went dark. Every
|
|
981
|
+
// `.status` read on a CAUGHT error in a rewritten file becomes `.code`.
|
|
982
|
+
// What is not mechanically rewritable is named at its line instead: a
|
|
983
|
+
// non-json `response`, options behind an identifier, a `.status` read on
|
|
984
|
+
// something that is not a catch binding.
|
|
985
|
+
// ---------------------------------------------------------------------------
|
|
986
|
+
const FORM_MANAGER_SPECIFIER = /(["'])__main_assets__\/js\/libs\/form-manager\.js\1/g;
|
|
987
|
+
const AUTHORIZED_FETCH_IMPORT = /^\s*import\s+[\w{},\s*]+\s+from\s+["']__main_assets__\/js\/libs\/authorized-fetch\.js["'];?\s*$/;
|
|
988
|
+
const CLIENT_DEFAULT_IMPORT = /import\s+omega\s+from\s+["']@omega\.js\/client["']/;
|
|
989
|
+
|
|
990
|
+
// `catch (err)`, `.catch((err) =>` and `.catch(err =>` — the three ways a
|
|
991
|
+
// consumer binds the error whose `.status` the rewrite has to move. The arrow
|
|
992
|
+
// with no parens is the commonest of them, and its terminator is the `=>`.
|
|
993
|
+
const CATCH_PARAM = /\bcatch\s*\(\s*\(?\s*([A-Za-z_$][\w$]*)\s*(?:[),]|=>)/g;
|
|
994
|
+
// One `response: <string>` option, with whichever comma separates it. The
|
|
995
|
+
// replacer keeps ONE of the two commas so the neighbours stay separated.
|
|
996
|
+
const RESPONSE_OPTION = /(,\s*)?\bresponse\s*:\s*(['"])([^'"]*)\2(\s*,)?/;
|
|
997
|
+
const STATUS_READ = /\b([A-Za-z_$][\w$]*)\.status\b/g;
|
|
998
|
+
|
|
999
|
+
// A `/` that opens a REGEX rather than dividing: what may precede one is an
|
|
1000
|
+
// operator, an opening bracket, or a keyword — never a value.
|
|
1001
|
+
const REGEX_PRECEDER = /(?:^|[(,=:[!&|?{};+\-*%~^<>]|\b(?:return|typeof|case|in|of|do|else|yield|await|new|delete|void))\s*$/;
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* The end of the string/template/regex literal opening at `start`.
|
|
1005
|
+
* @param {string} text
|
|
1006
|
+
* @param {number} start - index of the opening delimiter
|
|
1007
|
+
* @param {string} close - the delimiter that ends it
|
|
1008
|
+
* @param {boolean} multiline - whether it may cross a newline
|
|
1009
|
+
* @returns {number} index of the closing delimiter, -1 unterminated
|
|
1010
|
+
*/
|
|
1011
|
+
function literalEnd(text, start, close, multiline) {
|
|
1012
|
+
for (let i = start + 1; i < text.length; i++) {
|
|
1013
|
+
if (text[i] === '\\') i += 1;
|
|
1014
|
+
else if (text[i] === close) return i;
|
|
1015
|
+
else if (!multiline && text[i] === '\n') return -1;
|
|
1016
|
+
}
|
|
1017
|
+
return -1;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Walk JS source from `start`, handing `visit(char, index)` every character
|
|
1022
|
+
* that is real CODE — string, template, comment and regex bodies are SKIPPED.
|
|
1023
|
+
* A paren inside a url (`'/search('`) is text, and counting it desynced every
|
|
1024
|
+
* span this rule measures, which handed the option strip unrelated code.
|
|
1025
|
+
* @param {string} text
|
|
1026
|
+
* @param {number} start
|
|
1027
|
+
* @param {function(string, number): (number|undefined)} visit - a returned index stops the walk
|
|
1028
|
+
* @returns {number|null} what `visit` returned, or null when nothing stopped it
|
|
1029
|
+
*/
|
|
1030
|
+
function walkCode(text, start, visit) {
|
|
1031
|
+
for (let i = start; i < text.length; i++) {
|
|
1032
|
+
const char = text[i];
|
|
1033
|
+
const next = text[i + 1];
|
|
1034
|
+
|
|
1035
|
+
if (char === '/' && next === '/') {
|
|
1036
|
+
const line = text.indexOf('\n', i);
|
|
1037
|
+
if (line < 0) return null;
|
|
1038
|
+
i = line;
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (char === '/' && next === '*') {
|
|
1042
|
+
const close = text.indexOf('*/', i + 2);
|
|
1043
|
+
if (close < 0) return null;
|
|
1044
|
+
i = close + 1;
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
1048
|
+
const close = literalEnd(text, i, char, char === '`');
|
|
1049
|
+
if (close < 0) return null;
|
|
1050
|
+
i = close;
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
if (char === '/' && REGEX_PRECEDER.test(text.slice(Math.max(0, i - 12), i))) {
|
|
1054
|
+
const close = literalEnd(text, i, '/', false);
|
|
1055
|
+
if (close < 0) return null;
|
|
1056
|
+
i = close;
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const stop = visit(char, i);
|
|
1061
|
+
if (stop !== undefined) return stop;
|
|
1062
|
+
}
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* The index of the character closing the group opened at `open`.
|
|
1068
|
+
* @param {string} text
|
|
1069
|
+
* @param {number} open - index of the `(` or `{`
|
|
1070
|
+
* @returns {number|null} null when it never closes
|
|
1071
|
+
*/
|
|
1072
|
+
function groupEnd(text, open) {
|
|
1073
|
+
const closer = text[open] === '(' ? ')' : '}';
|
|
1074
|
+
let depth = 0;
|
|
1075
|
+
return walkCode(text, open, (char, index) => {
|
|
1076
|
+
if (char === text[open]) depth += 1;
|
|
1077
|
+
else if (char === closer) {
|
|
1078
|
+
depth -= 1;
|
|
1079
|
+
if (depth === 0) return index;
|
|
1080
|
+
}
|
|
1081
|
+
return undefined;
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* The end of the argument list opened at (entry, column) — parens balanced
|
|
1087
|
+
* across lines, string and comment bodies skipped. A call's options object is
|
|
1088
|
+
* the span this walk covers.
|
|
1089
|
+
* @param {Array<{ text: string }>} entries - the file's surviving lines
|
|
1090
|
+
* @param {number} start - index of the entry carrying the opening paren
|
|
1091
|
+
* @param {number} column - offset of the opening paren in that entry
|
|
1092
|
+
* @returns {number} index of the entry carrying the closing paren
|
|
1093
|
+
*/
|
|
1094
|
+
function argumentSpanEnd(entries, start, column) {
|
|
1095
|
+
const text = entries.slice(start).map((entry) => entry.text).join('\n');
|
|
1096
|
+
const close = groupEnd(text, column);
|
|
1097
|
+
// Never balanced: the span is the call's own line and nothing is stripped
|
|
1098
|
+
// below it. A run of the file's remaining lines is how a shape this walk
|
|
1099
|
+
// cannot read turns into edits to code that has nothing to do with the call.
|
|
1100
|
+
if (close === null) return start;
|
|
1101
|
+
return start + (text.slice(0, close).match(/\n/g) || []).length;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Every catch binding in a file, with the LINE SPAN of the block it is bound
|
|
1106
|
+
* over: a `{ … }` body, or an arrow's bare expression (which ends where the
|
|
1107
|
+
* `.catch(` call does).
|
|
1108
|
+
* @param {string} text - the whole source
|
|
1109
|
+
* @returns {Array<{ name: string, start: number, end: number }>} 1-based lines
|
|
1110
|
+
*/
|
|
1111
|
+
function catchScopes(text) {
|
|
1112
|
+
const lineAt = (index) => (text.slice(0, index).match(/\n/g) || []).length + 1;
|
|
1113
|
+
const scopes = [];
|
|
1114
|
+
|
|
1115
|
+
CATCH_PARAM.lastIndex = 0;
|
|
1116
|
+
let match;
|
|
1117
|
+
while ((match = CATCH_PARAM.exec(text)) !== null) {
|
|
1118
|
+
// Past whatever is left of the header (`) => `, a second parameter) to the
|
|
1119
|
+
// first character of the body itself.
|
|
1120
|
+
const body = walkCode(text, match.index + match[0].length, (char, index) => (/[\s),=>]/.test(char) ? undefined : index));
|
|
1121
|
+
if (body === null) continue;
|
|
1122
|
+
const end = text[body] === '{' ? groupEnd(text, body) : groupEnd(text, text.indexOf('(', match.index));
|
|
1123
|
+
if (end === null) continue;
|
|
1124
|
+
scopes.push({ name: match[1], start: lineAt(match.index), end: lineAt(end) });
|
|
1125
|
+
}
|
|
1126
|
+
return scopes;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const clientLibs = {
|
|
1130
|
+
id: 'client-libs',
|
|
1131
|
+
title: '`__main_assets__/js/libs/form-manager.js` → the client module; `authorizedFetch()` → `omega.request()`',
|
|
1132
|
+
apply(text) {
|
|
1133
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1134
|
+
const findings = [];
|
|
1135
|
+
// Every surviving line with the ORIGINAL line number it reports as: the
|
|
1136
|
+
// import line and an option-only line are removed, so the two numbering
|
|
1137
|
+
// schemes part ways immediately and every finding owes the source's.
|
|
1138
|
+
const entries = [];
|
|
1139
|
+
const removed = new Map(); // original line → what the edit records as `after`
|
|
1140
|
+
const callEntries = [];
|
|
1141
|
+
let rewroteCall = false;
|
|
1142
|
+
|
|
1143
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1144
|
+
const line = lines[i];
|
|
1145
|
+
|
|
1146
|
+
if (AUTHORIZED_FETCH_IMPORT.test(line)) {
|
|
1147
|
+
removed.set(i + 1, '(removed — omega.request() replaces it)');
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const replaced = line
|
|
1152
|
+
.replace(FORM_MANAGER_SPECIFIER, '$1@omega.js/client/modules/form-manager.js$1')
|
|
1153
|
+
.replace(/\bauthorizedFetch\(/g, 'omega.request(');
|
|
1154
|
+
if (/\bauthorizedFetch\(/.test(line)) {
|
|
1155
|
+
rewroteCall = true;
|
|
1156
|
+
callEntries.push(entries.length);
|
|
1157
|
+
findings.push({
|
|
1158
|
+
check: 'client-libs', line: i + 1, severity: 'warning',
|
|
1159
|
+
message: '`authorizedFetch(…)` → `omega.request(…)`: same (url, options) shape and the same Bearer attach, and the two dead shapes went with it '
|
|
1160
|
+
+ '(`response: \'json\'` dropped — the parsed body IS the return value; `err.status` → `err.code`, where @omega.js/client puts the HTTP status). '
|
|
1161
|
+
+ 'What is left is yours to verify: `output`, and the route segment (`/backend-manager/` is now `/omega/`)',
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
entries.push({ line: i + 1, text: replaced });
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
if (rewroteCall) {
|
|
1168
|
+
// ---- the options of each rewritten call
|
|
1169
|
+
for (const start of callEntries) {
|
|
1170
|
+
const column = entries[start].text.indexOf('omega.request(') + 'omega.request'.length;
|
|
1171
|
+
const end = argumentSpanEnd(entries, start, column);
|
|
1172
|
+
let sawOptions = false;
|
|
1173
|
+
|
|
1174
|
+
for (let i = start; i <= end; i++) {
|
|
1175
|
+
const match = entries[i].text.match(RESPONSE_OPTION);
|
|
1176
|
+
if (!match) continue;
|
|
1177
|
+
sawOptions = true;
|
|
1178
|
+
if (match[3] !== 'json') {
|
|
1179
|
+
findings.push({
|
|
1180
|
+
check: 'client-libs', line: entries[i].line, severity: 'warning',
|
|
1181
|
+
message: `\`response: '${match[3]}'\` has no @omega.js/client equivalent — \`omega.request\` parses by content type and returns the body. Port this call's output handling by hand`,
|
|
1182
|
+
});
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
const stripped = entries[i].text
|
|
1186
|
+
.replace(RESPONSE_OPTION, (whole, lead, quote, value, trail) => (lead && trail ? trail : ''))
|
|
1187
|
+
.replace(/\{\s+\}/, '{}');
|
|
1188
|
+
if (stripped.trim() === '') removed.set(entries[i].line, '(removed — omega.request returns the parsed body)');
|
|
1189
|
+
else entries[i].text = stripped;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Options behind an identifier (`omega.request(url, OPTIONS)`): a text
|
|
1193
|
+
// codemod cannot see inside them, and they are exactly where the dead
|
|
1194
|
+
// wonderful-fetch keys hide.
|
|
1195
|
+
const spanText = entries.slice(start, end + 1).map((entry) => entry.text).join('\n');
|
|
1196
|
+
if (!sawOptions && /,\s*[A-Za-z_$][\w$]*\s*\)/.test(spanText)) {
|
|
1197
|
+
findings.push({
|
|
1198
|
+
check: 'client-libs', line: entries[start].line, severity: 'warning',
|
|
1199
|
+
message: 'the options come from a variable — check it by hand for the wonderful-fetch keys (`response`, `output`) @omega.js/client does not have',
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// ---- the error shape: authorizedFetch WAS this file's fetch, so an
|
|
1205
|
+
// error caught here is one of its errors — but only INSIDE the block that
|
|
1206
|
+
// binds it. Rewriting every `<name>.status` in the file by name renamed a
|
|
1207
|
+
// same-named parameter of an unrelated function.
|
|
1208
|
+
const scopes = catchScopes(lines.join('\n'));
|
|
1209
|
+
|
|
1210
|
+
for (const entry of entries) {
|
|
1211
|
+
entry.text = entry.text.replace(STATUS_READ, (read, identifier) => {
|
|
1212
|
+
const bound = scopes.some((scope) => scope.name === identifier && entry.line >= scope.start && entry.line <= scope.end);
|
|
1213
|
+
if (bound) return `${identifier}.code`;
|
|
1214
|
+
findings.push({
|
|
1215
|
+
check: 'client-libs', line: entry.line, severity: 'warning',
|
|
1216
|
+
message: `left \`${read}\` alone — @omega.js/client puts the HTTP status on \`error.code\` (request.js), and only the author knows whose \`.status\` this is`,
|
|
1217
|
+
});
|
|
1218
|
+
return read;
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// The edits, in source order: a removed line, then any line whose text moved.
|
|
1224
|
+
const edits = [];
|
|
1225
|
+
for (const entry of entries) {
|
|
1226
|
+
const before = lines[entry.line - 1];
|
|
1227
|
+
if (removed.has(entry.line)) continue;
|
|
1228
|
+
if (entry.text !== before) edits.push({ rule: 'client-libs', line: entry.line, before: before.trim(), after: entry.text.trim() });
|
|
1229
|
+
}
|
|
1230
|
+
for (const [line, after] of removed) edits.push({ rule: 'client-libs', line, before: lines[line - 1].trim(), after });
|
|
1231
|
+
edits.sort((a, b) => a.line - b.line);
|
|
1232
|
+
|
|
1233
|
+
// `omega.request()` needs the singleton in scope. Every real consumer that
|
|
1234
|
+
// called authorizedFetch also imported web-manager (rule 13 renames that
|
|
1235
|
+
// import ahead of this one), so a miss here is a genuinely odd file — say
|
|
1236
|
+
// so rather than emit a module that throws on first call.
|
|
1237
|
+
const result = fromLines(entries.filter((entry) => !removed.has(entry.line)).map((entry) => entry.text), trailingNewline);
|
|
1238
|
+
if (rewroteCall && !CLIENT_DEFAULT_IMPORT.test(result)) {
|
|
1239
|
+
findings.push({
|
|
1240
|
+
check: 'client-libs', line: 1, severity: 'error',
|
|
1241
|
+
message: 'rewrote `authorizedFetch()` to `omega.request()` but the file has no client singleton — add `import omega from \'@omega.js/client\';`',
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
return { text: result, edits, findings };
|
|
1246
|
+
},
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
// ---------------------------------------------------------------------------
|
|
1250
|
+
// Rule 21 — Kramdown inline attribute lists
|
|
1251
|
+
// ([#565](https://github.com/Omega-JS-Stack/omega/issues/565))
|
|
1252
|
+
//
|
|
1253
|
+
// UJM page bodies wear IALs (`[Get your API key](/account){: .btn .btn-lg }`)
|
|
1254
|
+
// because Jekyll renders markdown with Kramdown. `@omega.js/web` renders it
|
|
1255
|
+
// with bare markdown-it, so the link ships UNSTYLED and the `{: … }` PRINTS on
|
|
1256
|
+
// the page. The framework stays plugin-free (Ian's ruling 2026-08-21: no
|
|
1257
|
+
// standing compat machinery in framework runs), so the conversion is here,
|
|
1258
|
+
// once: a class-only IAL on a link IS an anchor with that class list.
|
|
1259
|
+
//
|
|
1260
|
+
// MARKDOWN FILES ONLY. An `.html` page was never markdown-rendered — under
|
|
1261
|
+
// Jekyll too its `[text](url){: … }` was literal text — so rewriting it there
|
|
1262
|
+
// would invent a link the legacy page never had.
|
|
1263
|
+
//
|
|
1264
|
+
// Anything else wearing an IAL (a block-level one under a heading, a link IAL
|
|
1265
|
+
// carrying an id or an attribute) is reported: markdown-it prints all of them,
|
|
1266
|
+
// and their targets are not one mechanical shape.
|
|
1267
|
+
// ---------------------------------------------------------------------------
|
|
1268
|
+
const MARKDOWN_FILE = /\.(?:md|markdown)$/i;
|
|
1269
|
+
const CLASS_ONLY_LINK_IAL = /\[([^\]\n]*)\]\((\S+)\)\{:\s*((?:\.[\w-]+\s*)+)\}/g;
|
|
1270
|
+
const SURVIVING_IAL = /\{:(?:\s|[.#])/;
|
|
1271
|
+
|
|
1272
|
+
const markdownIal = {
|
|
1273
|
+
id: 'markdown-ial',
|
|
1274
|
+
title: '`[text](url){: .a .b }` → `<a href="url" class="a b">text</a>`',
|
|
1275
|
+
apply(text, ctx = {}) {
|
|
1276
|
+
if (!MARKDOWN_FILE.test(ctx.filePath || '')) return { text, edits: [], findings: [] };
|
|
1277
|
+
|
|
1278
|
+
const { text: out, edits } = replacePerLine(
|
|
1279
|
+
'markdown-ial', text, CLASS_ONLY_LINK_IAL,
|
|
1280
|
+
(whole, label, href, classes) => `<a href="${href}" class="${classes.trim().split(/\s+/).map((name) => name.slice(1)).join(' ')}">${label}</a>`
|
|
1281
|
+
);
|
|
1282
|
+
|
|
1283
|
+
const findings = [];
|
|
1284
|
+
const { lines } = toLines(out);
|
|
1285
|
+
lines.forEach((line, index) => {
|
|
1286
|
+
if (!SURVIVING_IAL.test(line)) return;
|
|
1287
|
+
findings.push({
|
|
1288
|
+
check: 'markdown-ial', line: index + 1, severity: 'error',
|
|
1289
|
+
message: 'Kramdown inline attribute list survives — markdown-it does not read them, so this `{: … }` PRINTS on the page '
|
|
1290
|
+
+ 'and whatever it decorated renders unstyled. A class-only IAL on a link converts automatically; this one carries an id, '
|
|
1291
|
+
+ 'an attribute, or a block-level target — write the element as HTML by hand',
|
|
1292
|
+
});
|
|
1293
|
+
});
|
|
1294
|
+
return { text: out, edits, findings };
|
|
1295
|
+
},
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
// ---------------------------------------------------------------------------
|
|
1299
|
+
// Rule 20 — the retired `modules/adunits/*` includes
|
|
1300
|
+
// ([#559](https://github.com/Omega-JS-Stack/omega/issues/559))
|
|
1301
|
+
//
|
|
1302
|
+
// The ads rebuild (docs/web/ads-system.md) retired the UJM adunit includes for
|
|
1303
|
+
// ONE section, `verts/unit`, whose fallback ladder covers what adsense.html
|
|
1304
|
+
// and promo-server.html did separately. An include the engine cannot resolve
|
|
1305
|
+
// renders nothing and fails nothing, so a ported page loses its ad unit
|
|
1306
|
+
// silently.
|
|
1307
|
+
//
|
|
1308
|
+
// Only adsense.html maps mechanically: `type` carries, and UJM's `vert-size`
|
|
1309
|
+
// (named around Liquid's built-in `size` filter) IS the section's `size`.
|
|
1310
|
+
// Values pass through verbatim — a section tag's inline args are Liquid
|
|
1311
|
+
// expressions, and its strings liquify at the call site.
|
|
1312
|
+
//
|
|
1313
|
+
// Runs AFTER include-slash, which owns the leading `/`.
|
|
1314
|
+
// ---------------------------------------------------------------------------
|
|
1315
|
+
const ADUNIT_INCLUDE = /\{%(-?)\s*include(?:_cached)?\s+modules\/adunits\/([\w-]+)\.html([^%]*?)(-?)%\}/g;
|
|
1316
|
+
const INCLUDE_ARG = /([\w-]+)\s*=\s*("[^"]*"|'[^']*'|\S+)/g;
|
|
1317
|
+
const ADUNIT_ARGS = { type: 'type', 'vert-size': 'size' };
|
|
1318
|
+
const VERT_UNIT_POINTER = 'the successor is `{% section "verts/unit", type: …, size: … %}` (docs/web/ads-system.md)';
|
|
1319
|
+
|
|
1320
|
+
const adunitSection = {
|
|
1321
|
+
id: 'adunit-section',
|
|
1322
|
+
title: '`{% include modules/adunits/adsense.html %}` → `{% section "verts/unit" %}`',
|
|
1323
|
+
apply(text) {
|
|
1324
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1325
|
+
const edits = [];
|
|
1326
|
+
const findings = [];
|
|
1327
|
+
const out = lines.map((line, index) => {
|
|
1328
|
+
const replaced = line.replace(ADUNIT_INCLUDE, (whole, open, unit, argText, close) => {
|
|
1329
|
+
if (unit !== 'adsense') {
|
|
1330
|
+
findings.push({
|
|
1331
|
+
check: 'adunit-section', line: index + 1, severity: 'error',
|
|
1332
|
+
message: `\`modules/adunits/${unit}.html\` is RETIRED — the include resolves to nothing and the unit silently vanishes. `
|
|
1333
|
+
+ `${VERT_UNIT_POINTER}; port this one by hand (in-house inventory is \`type: "house"\` with \`vert_id\`)`,
|
|
1334
|
+
});
|
|
1335
|
+
return whole;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const pairs = [];
|
|
1339
|
+
const dropped = [];
|
|
1340
|
+
for (const [, key, value] of argText.matchAll(INCLUDE_ARG)) {
|
|
1341
|
+
if (ADUNIT_ARGS[key]) pairs.push(`${ADUNIT_ARGS[key]}: ${value}`);
|
|
1342
|
+
else dropped.push(key);
|
|
1343
|
+
}
|
|
1344
|
+
if (dropped.length > 0) {
|
|
1345
|
+
findings.push({
|
|
1346
|
+
check: 'adunit-section', line: index + 1, severity: 'warning',
|
|
1347
|
+
message: `\`${dropped.join('`, `')}\` did not carry onto \`verts/unit\` — the section has no such arg. `
|
|
1348
|
+
+ 'The AdSense slot comes from `advertising.providers.adsense` config, and the unit owns its own markup',
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
return `{%${open} section "verts/unit"${pairs.map((pair) => `, ${pair}`).join('')} ${close}%}`;
|
|
1352
|
+
});
|
|
1353
|
+
if (replaced !== line) edits.push({ rule: 'adunit-section', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
1354
|
+
return replaced;
|
|
1355
|
+
});
|
|
1356
|
+
return { text: fromLines(out, trailingNewline), edits, findings };
|
|
1357
|
+
},
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
// ---------------------------------------------------------------------------
|
|
1361
|
+
// Rule 19 — the UJM library accessor
|
|
1362
|
+
// ([#560](https://github.com/Omega-JS-Stack/omega/issues/560))
|
|
1363
|
+
//
|
|
1364
|
+
// Rule 13 renames the singleton and stops there, so `webManager.uj()` ships as
|
|
1365
|
+
// `omega.uj()` — build-clean and DEAD (`@omega.js/client` has no `uj`; the
|
|
1366
|
+
// first call is a TypeError). The successor is `omega.library()`, the accessor
|
|
1367
|
+
// the web runtime installs on the singleton (core/js/main.js).
|
|
1368
|
+
//
|
|
1369
|
+
// The rewrite claims the two receivers that ARE the singleton and the no-arg
|
|
1370
|
+
// call `uj()` always was. Anything else wearing `.uj(` is reported: an unknown
|
|
1371
|
+
// receiver is not mechanically this accessor, and a silent rewrite there would
|
|
1372
|
+
// trade one runtime TypeError for another.
|
|
1373
|
+
// ---------------------------------------------------------------------------
|
|
1374
|
+
const UJ_ACCESSOR = /\b(?:webManager|omega)\.uj\(\s*\)/g;
|
|
1375
|
+
const UJ_SURVIVOR = /\.uj\(/;
|
|
1376
|
+
|
|
1377
|
+
const ujLibrary = {
|
|
1378
|
+
id: 'uj-library',
|
|
1379
|
+
title: '`webManager.uj()`/`omega.uj()` → `omega.library()`',
|
|
1380
|
+
apply(text) {
|
|
1381
|
+
const { text: out, edits } = replacePerLine('uj-library', text, UJ_ACCESSOR, 'omega.library()');
|
|
1382
|
+
const findings = [];
|
|
1383
|
+
const { lines } = toLines(out);
|
|
1384
|
+
lines.forEach((line, index) => {
|
|
1385
|
+
if (!UJ_SURVIVOR.test(line)) return;
|
|
1386
|
+
findings.push({
|
|
1387
|
+
check: 'uj-library', line: index + 1, severity: 'error',
|
|
1388
|
+
message: '`.uj(` survives — `@omega.js/client` has no `uj()`, so this call is a runtime TypeError. '
|
|
1389
|
+
+ 'The UJM library accessor is `omega.library()` on the client singleton; rewrite this receiver by hand',
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1392
|
+
return { text: out, edits, findings };
|
|
1393
|
+
},
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
// ---------------------------------------------------------------------------
|
|
1397
|
+
// Rule 15 — the service-worker entry's framework import
|
|
1398
|
+
// (only the /service-worker subpath: the bare `ultimate-jekyll-manager` import
|
|
1399
|
+
// is the asset layer's main.js, owned by consumer-assets.js)
|
|
1400
|
+
// ---------------------------------------------------------------------------
|
|
1401
|
+
const serviceWorkerImport = {
|
|
1402
|
+
id: 'service-worker-import',
|
|
1403
|
+
title: "`'ultimate-jekyll-manager/service-worker'` → `'@omega.js/web/service-worker'`",
|
|
1404
|
+
apply(text) {
|
|
1405
|
+
const { text: out, edits } = replacePerLine(
|
|
1406
|
+
'service-worker-import', text,
|
|
1407
|
+
/(["'])ultimate-jekyll-manager\/service-worker\1/g,
|
|
1408
|
+
'$1@omega.js/web/service-worker$1'
|
|
1409
|
+
);
|
|
1410
|
+
return { text: out, edits, findings: [] };
|
|
1411
|
+
},
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
// ---------------------------------------------------------------------------
|
|
1415
|
+
// Rule 16 — the legacy classy gradient utilities → the v2 dotgrid hero
|
|
1416
|
+
// ([#296](https://github.com/Omega-JS-Stack/omega/issues/296))
|
|
1417
|
+
//
|
|
1418
|
+
// UJM-era heroes wear `.gradient-animated` (a 5s shimmer over the gradient
|
|
1419
|
+
// background) and `.gradient-grain` (a ::before noise overlay). Classy v2
|
|
1420
|
+
// ships NEITHER — its binding constraint is zero gradients (classy-v2
|
|
1421
|
+
// DIRECTION.md) — so those classes silently do nothing on a ported page.
|
|
1422
|
+
// The conversion is once, here: v2's Marketing DNA puts a "subtle dotted grid
|
|
1423
|
+
// (masked)" behind every hero, and that is `.omega-dotgrid`
|
|
1424
|
+
// (themes/classy/css/base/_utilities.scss) — the same shape the grain had, a
|
|
1425
|
+
// masked ::before backdrop with the content lifted above it. The animated
|
|
1426
|
+
// half becomes the dotgrid's LIVE canvas, opted in per element with
|
|
1427
|
+
// `data-omega-dotfield` exactly as every packaged hero authors the pair. Both
|
|
1428
|
+
// classes on one element collapse to ONE `omega-dotgrid`.
|
|
1429
|
+
//
|
|
1430
|
+
// `.bg-gradient-*` is NOT in scope: v2 deliberately keeps those names,
|
|
1431
|
+
// neutralized to flat token paint for straggler markup.
|
|
1432
|
+
// ---------------------------------------------------------------------------
|
|
1433
|
+
const LEGACY_GRADIENT_CLASSES = /(?<![\w-])gradient-(?:animated|grain)(?![\w-])/g;
|
|
1434
|
+
const GRADIENT_ANIMATED = /(?<![\w-])gradient-animated(?![\w-])/;
|
|
1435
|
+
const DOTGRID_CLASS = /(?<![\w-])omega-dotgrid(?![\w-])/;
|
|
1436
|
+
// Each occurrence with the whitespace ahead of it — dropping a duplicate takes
|
|
1437
|
+
// its separator along instead of leaving a double space in the class list.
|
|
1438
|
+
const DOTGRID_OCCURRENCE = /\s*(?<![\w-])omega-dotgrid(?![\w-])/g;
|
|
1439
|
+
const CLASS_ATTRIBUTE = /class\s*=\s*(["'])([^"']*)\1/g;
|
|
1440
|
+
const LIQUID_VALUE = /\{[{%]/;
|
|
1441
|
+
|
|
1442
|
+
const gradientUtilities = {
|
|
1443
|
+
id: 'gradient-utilities',
|
|
1444
|
+
title: '`gradient-animated`/`gradient-grain` → `omega-dotgrid` (+ `data-omega-dotfield` for the motion)',
|
|
1445
|
+
apply(text) {
|
|
1446
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1447
|
+
const edits = [];
|
|
1448
|
+
const out = lines.map((line, index) => {
|
|
1449
|
+
let replaced = line.replace(LEGACY_GRADIENT_CLASSES, 'omega-dotgrid');
|
|
1450
|
+
if (replaced === line) return line;
|
|
1451
|
+
|
|
1452
|
+
replaced = replaced.replace(CLASS_ATTRIBUTE, (whole, quote, value) => {
|
|
1453
|
+
// A class list Liquid assembles may emit its dotgrid conditionally —
|
|
1454
|
+
// there a "duplicate" is the only class the other branch has, so the
|
|
1455
|
+
// literal duplicate (harmless in a class attribute) stays.
|
|
1456
|
+
if (LIQUID_VALUE.test(value)) return whole;
|
|
1457
|
+
let kept = false;
|
|
1458
|
+
const collapsed = value.replace(DOTGRID_OCCURRENCE, (occurrence) => {
|
|
1459
|
+
if (kept) return '';
|
|
1460
|
+
kept = true;
|
|
1461
|
+
return occurrence;
|
|
1462
|
+
});
|
|
1463
|
+
return `class=${quote}${collapsed}${quote}`;
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
// The live canvas rides the element that carries the class — and only
|
|
1467
|
+
// when the page asked for motion in the first place.
|
|
1468
|
+
if (GRADIENT_ANIMATED.test(line) && !/data-omega-dotfield/.test(replaced)) {
|
|
1469
|
+
replaced = replaced.replace(CLASS_ATTRIBUTE, (whole, quote, value) =>
|
|
1470
|
+
(DOTGRID_CLASS.test(value) ? `${whole} data-omega-dotfield` : whole));
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
edits.push({ rule: 'gradient-utilities', line: index + 1, before: line.trim(), after: replaced.trim() });
|
|
1474
|
+
return replaced;
|
|
1475
|
+
});
|
|
1476
|
+
return { text: fromLines(out, trailingNewline), edits, findings: [] };
|
|
1477
|
+
},
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
// ---------------------------------------------------------------------------
|
|
1481
|
+
// Rule 17 — the dead `asset_path` frontmatter key
|
|
1482
|
+
// ([#470](https://github.com/Omega-JS-Stack/omega/issues/470))
|
|
1483
|
+
//
|
|
1484
|
+
// UJM pages used it to borrow another page's module (agents/new.md carrying
|
|
1485
|
+
// `asset_path: dashboard/agents/edit`). Page assets now resolve by URL ALONE
|
|
1486
|
+
// (spec §7), so the key is inert: the page renders and ships with no js/css.
|
|
1487
|
+
// Nothing is rewritten — the successor is a FILE the porter adds, so this is a
|
|
1488
|
+
// finding that names the key and the idiom, never a generic ignored-key note.
|
|
1489
|
+
//
|
|
1490
|
+
// The successor named FIRST is the `[name]` wildcard family file, because the
|
|
1491
|
+
// key's real job was a FAMILY of URLs sharing one module — the re-exporting
|
|
1492
|
+
// index.js is the one-off escape hatch, second (#470 amendment).
|
|
1493
|
+
// ---------------------------------------------------------------------------
|
|
1494
|
+
const ASSET_PATH_KEY = /^asset_path\s*:/;
|
|
1495
|
+
|
|
1496
|
+
const assetPath = {
|
|
1497
|
+
id: 'asset-path',
|
|
1498
|
+
title: '`asset_path:` frontmatter → a `[name]` wildcard page module (reported, never rewritten)',
|
|
1499
|
+
apply(text) {
|
|
1500
|
+
const { lines } = toLines(text);
|
|
1501
|
+
// Scoped to the leading `---` fence: `asset_path:` in the body is prose.
|
|
1502
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
1503
|
+
if (fenceEnd < 0) return { text, edits: [], findings: [] };
|
|
1504
|
+
|
|
1505
|
+
const findings = [];
|
|
1506
|
+
lines.forEach((line, index) => {
|
|
1507
|
+
if (index === 0 || index >= fenceEnd || !ASSET_PATH_KEY.test(line)) return;
|
|
1508
|
+
findings.push({
|
|
1509
|
+
check: 'asset-path', line: index + 1, severity: 'error',
|
|
1510
|
+
message: '`asset_path` is DEAD — page assets resolve by URL alone (spec §7), so this page ships with NO js/css. '
|
|
1511
|
+
+ 'A FAMILY of pages sharing one module is a `[name]` wildcard file: `js/pages/blog/[slug].js` + `css/pages/blog/[slug].scss` '
|
|
1512
|
+
+ 'serve every /blog/<slug> page, no frontmatter involved. '
|
|
1513
|
+
+ 'For a ONE-OFF page borrowing another page\'s module: a re-exporting `js/pages/<this page>/index.js` '
|
|
1514
|
+
+ '(`export { default } from \'../<other page>/index.js\';`) and a `css/pages/<this page>/index.scss` that `@use`s the other sheet',
|
|
1515
|
+
});
|
|
1516
|
+
});
|
|
1517
|
+
return { text, edits: [], findings };
|
|
1518
|
+
},
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
// ---------------------------------------------------------------------------
|
|
1522
|
+
// Rule 18 — the UJM → OMEGA section-arg renames
|
|
1523
|
+
// ([#545](https://github.com/Omega-JS-Stack/omega/issues/545))
|
|
1524
|
+
//
|
|
1525
|
+
// Three section args were spelled differently in UJM, and section-arg
|
|
1526
|
+
// validation is warn-only: a migrated page keeps the old key, the section
|
|
1527
|
+
// never reads it, and the eyebrow / CTA sub line / CTA button simply vanish.
|
|
1528
|
+
// Deterministic one-liners, so they belong in the table exactly like the
|
|
1529
|
+
// client-runtime renames (#248).
|
|
1530
|
+
//
|
|
1531
|
+
// Scoped to the leading `---` fence's TOP-LEVEL section blocks and to their
|
|
1532
|
+
// DIRECT children: a UJM pricing plan's `tagline:` is a plan's tagline, and
|
|
1533
|
+
// `hero.demo.options.button` is the demo lane's own button.
|
|
1534
|
+
// ---------------------------------------------------------------------------
|
|
1535
|
+
const SECTION_ARGS = {
|
|
1536
|
+
hero: { tagline: 'badge.text' },
|
|
1537
|
+
cta: { description: 'subheadline', button: 'primary_button' },
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
const YAML_KEY = /^(\s*)([\w-]+)\s*:/;
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* Read a frontmatter block: the indent of its direct children and the keys
|
|
1544
|
+
* they declare (needed before rewriting — `hero.badge` may already be taken by
|
|
1545
|
+
* the legacy badge object).
|
|
1546
|
+
* @param {string[]} lines
|
|
1547
|
+
* @param {number} start - the block's opener line index
|
|
1548
|
+
* @param {number} end - the frontmatter fence's closing line index
|
|
1549
|
+
* @param {number} [openerLead] - the opener's own indent (0 for a top-level
|
|
1550
|
+
* block); the first line back at or above it ends the block
|
|
1551
|
+
* @returns {{ indent: number|null, keys: Set<string> }}
|
|
1552
|
+
*/
|
|
1553
|
+
function readSectionBlock(lines, start, end, openerLead = 0) {
|
|
1554
|
+
let indent = null;
|
|
1555
|
+
const keys = new Set();
|
|
1556
|
+
for (let i = start + 1; i < end; i++) {
|
|
1557
|
+
const line = lines[i];
|
|
1558
|
+
if (!line.trim() || /^\s*#/.test(line)) continue;
|
|
1559
|
+
const lead = (line.match(/^\s*/) || [''])[0].length;
|
|
1560
|
+
if (lead <= openerLead) break; // a sibling (or the next top-level key) ends the block
|
|
1561
|
+
if (indent === null) indent = lead;
|
|
1562
|
+
const match = line.match(YAML_KEY);
|
|
1563
|
+
if (match && lead === indent) keys.add(match[2]);
|
|
1564
|
+
}
|
|
1565
|
+
return { indent, keys };
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
const sectionArgs = {
|
|
1569
|
+
id: 'section-args',
|
|
1570
|
+
title: '`hero.tagline` → `hero.badge.text`; `cta.description` → `cta.subheadline`; `cta.button` → `cta.primary_button`',
|
|
1571
|
+
apply(text) {
|
|
1572
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1573
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
1574
|
+
if (fenceEnd < 0) return { text, edits: [], findings: [] };
|
|
1575
|
+
|
|
1576
|
+
const edits = [];
|
|
1577
|
+
const findings = [];
|
|
1578
|
+
const out = [];
|
|
1579
|
+
let block = null;
|
|
1580
|
+
|
|
1581
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1582
|
+
const line = lines[i];
|
|
1583
|
+
if (i === 0 || i >= fenceEnd) { out.push(line); block = null; continue; }
|
|
1584
|
+
// A top-level key (or any unindented content) ends the block above it
|
|
1585
|
+
if (line.trim() && !/^\s/.test(line)) block = null;
|
|
1586
|
+
|
|
1587
|
+
const opener = line.match(/^([\w-]+):\s*$/);
|
|
1588
|
+
if (opener && SECTION_ARGS[opener[1]]) {
|
|
1589
|
+
const { indent, keys } = readSectionBlock(lines, i, fenceEnd);
|
|
1590
|
+
block = indent === null ? null : { renames: SECTION_ARGS[opener[1]], indent, keys };
|
|
1591
|
+
out.push(line);
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
const child = block && line.match(new RegExp(`^(\\s{${block.indent}})([\\w-]+)(\\s*:)(.*)$`));
|
|
1596
|
+
const target = child && block.renames[child[2]];
|
|
1597
|
+
if (!target) { out.push(line); continue; }
|
|
1598
|
+
|
|
1599
|
+
const [, lead, key, colon, value] = child;
|
|
1600
|
+
if (!target.includes('.')) {
|
|
1601
|
+
const replaced = `${lead}${target}${colon}${value}`;
|
|
1602
|
+
edits.push({ rule: 'section-args', line: i + 1, before: line.trim(), after: replaced.trim() });
|
|
1603
|
+
out.push(replaced);
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// The nested target (`badge.text`) becomes a real two-line mapping — but
|
|
1608
|
+
// only when the block has no `badge:` of its own: the UJM badge is a
|
|
1609
|
+
// DIFFERENT object ({ title, subtitle, icon }), and a second key of the
|
|
1610
|
+
// same name would drop one of the two.
|
|
1611
|
+
const [parent, leaf] = target.split('.');
|
|
1612
|
+
if (block.keys.has(parent)) {
|
|
1613
|
+
findings.push({
|
|
1614
|
+
check: 'section-args', line: i + 1, severity: 'error',
|
|
1615
|
+
message: `\`${key}\` here is now \`${parent}.${leaf}\`, but this block already declares \`${parent}:\` `
|
|
1616
|
+
+ '(the UJM badge object) — merge the two by hand; a second key of the same name would drop one',
|
|
1617
|
+
});
|
|
1618
|
+
out.push(line);
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
if (!value.trim()) {
|
|
1622
|
+
findings.push({
|
|
1623
|
+
check: 'section-args', line: i + 1, severity: 'error',
|
|
1624
|
+
message: `\`${key}\` here is now \`${parent}.${leaf}\`, but this one carries a block, not a string — re-nest it by hand`,
|
|
1625
|
+
});
|
|
1626
|
+
out.push(line);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const nested = `${lead.repeat(2)}${leaf}${colon}${value}`;
|
|
1631
|
+
out.push(`${lead}${parent}:`, nested);
|
|
1632
|
+
edits.push({ rule: 'section-args', line: i + 1, before: line.trim(), after: `${parent}: { ${leaf}:${value} }`.trim() });
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
return { text: fromLines(out, trailingNewline), edits, findings };
|
|
1636
|
+
},
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
// ---------------------------------------------------------------------------
|
|
1640
|
+
// Rule 22 — the hero's silently-dropped secondary button
|
|
1641
|
+
// ([#579](https://github.com/Omega-JS-Stack/omega/issues/579))
|
|
1642
|
+
//
|
|
1643
|
+
// `marketing/hero` defaults `secondary_button.enabled: false` (the button is
|
|
1644
|
+
// opt-in per its section.json5, and that default STAYS — a theme's own heroes
|
|
1645
|
+
// depend on it). A UJM page never authored an `enabled` key: writing the block
|
|
1646
|
+
// WAS the opt-in. So a migrated page keeps every button arg, builds clean,
|
|
1647
|
+
// looks right — and has no second CTA. Migrate answers the question the legacy
|
|
1648
|
+
// page answered by existing, and the report says it did.
|
|
1649
|
+
//
|
|
1650
|
+
// Scoped like rule 18: a top-level `hero:` block's DIRECT child. `cta`'s
|
|
1651
|
+
// secondary button has no such default (it renders whenever it is set), so it
|
|
1652
|
+
// is deliberately not here.
|
|
1653
|
+
// ---------------------------------------------------------------------------
|
|
1654
|
+
const heroSecondaryButton = {
|
|
1655
|
+
id: 'hero-secondary-button',
|
|
1656
|
+
title: '`hero.secondary_button` without `enabled` → `enabled: true`',
|
|
1657
|
+
apply(text) {
|
|
1658
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1659
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
1660
|
+
if (fenceEnd < 0) return { text, edits: [], findings: [] };
|
|
1661
|
+
|
|
1662
|
+
const edits = [];
|
|
1663
|
+
const findings = [];
|
|
1664
|
+
const out = [];
|
|
1665
|
+
let block = null;
|
|
1666
|
+
|
|
1667
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1668
|
+
const line = lines[i];
|
|
1669
|
+
out.push(line);
|
|
1670
|
+
if (i === 0 || i >= fenceEnd) { block = null; continue; }
|
|
1671
|
+
if (line.trim() && !/^\s/.test(line)) block = /^hero:\s*$/.test(line) ? readSectionBlock(lines, i, fenceEnd) : null;
|
|
1672
|
+
if (!block || block.indent === null) continue;
|
|
1673
|
+
|
|
1674
|
+
// The opener alone — an inline mapping carries its own value and is not
|
|
1675
|
+
// the shape a UJM page ever wrote.
|
|
1676
|
+
if (line !== `${' '.repeat(block.indent)}secondary_button:`) continue;
|
|
1677
|
+
|
|
1678
|
+
const { indent, keys } = readSectionBlock(lines, i, fenceEnd, block.indent);
|
|
1679
|
+
if (indent === null || keys.has('enabled')) continue;
|
|
1680
|
+
|
|
1681
|
+
const enabled = `${' '.repeat(indent)}enabled: true`;
|
|
1682
|
+
out.push(enabled);
|
|
1683
|
+
edits.push({ rule: 'hero-secondary-button', line: i + 1, before: line.trim(), after: enabled.trim() });
|
|
1684
|
+
findings.push({
|
|
1685
|
+
check: 'hero-secondary-button', line: i + 1, severity: 'warning',
|
|
1686
|
+
message: 'this hero authored a secondary button but no `enabled` key, and `marketing/hero` defaults it OFF — '
|
|
1687
|
+
+ 'migrate wrote `enabled: true` so the CTA survives the port. Delete the line if the button was meant to be hidden',
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
return { text: fromLines(out, trailingNewline), edits, findings };
|
|
1692
|
+
},
|
|
1693
|
+
};
|
|
1694
|
+
|
|
1695
|
+
// ---------------------------------------------------------------------------
|
|
1696
|
+
// Rule 23 — bare config sections in frontmatter → under a `config:` parent
|
|
1697
|
+
// (#607). A UJM-era page restated omega.json5 keys bare (`theme:`, `client:`,
|
|
1698
|
+
// `inbound:`), which merged into the same flat tree the config seeded and let
|
|
1699
|
+
// the two namespaces drift. The page lane is `config:` now and the build
|
|
1700
|
+
// ERRORS on a bare config section — so this rule is what carries a legacy page
|
|
1701
|
+
// across. Page machinery (`meta:`, `schema:`) is not config and stays put.
|
|
1702
|
+
//
|
|
1703
|
+
// Runs LAST with the other inserting rules: it re-indents whole blocks, so
|
|
1704
|
+
// every line-numbered finding above it reports the pre-rewrite line.
|
|
1705
|
+
// ---------------------------------------------------------------------------
|
|
1706
|
+
const configParent = {
|
|
1707
|
+
id: 'config-parent',
|
|
1708
|
+
title: 'bare config sections in frontmatter → under `config:`',
|
|
1709
|
+
apply(text) {
|
|
1710
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1711
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
1712
|
+
if (fenceEnd < 0) return { text, edits: [], findings: [] };
|
|
1713
|
+
|
|
1714
|
+
// Split the frontmatter into top-level blocks: an unindented `key:` opens
|
|
1715
|
+
// one and everything under it (indented lines, blanks) rides along until
|
|
1716
|
+
// the next unindented line. A comment ABOVE a key belongs to that key —
|
|
1717
|
+
// it is the note explaining the block, and moving the block without it
|
|
1718
|
+
// would strand the note over a `config:` line it never described.
|
|
1719
|
+
const blocks = [];
|
|
1720
|
+
let current = null;
|
|
1721
|
+
let lead = [];
|
|
1722
|
+
for (let i = 1; i < fenceEnd; i++) {
|
|
1723
|
+
const line = lines[i];
|
|
1724
|
+
const opener = line.match(/^([\w-]+):/);
|
|
1725
|
+
if (opener) {
|
|
1726
|
+
current = { key: opener[1], line: i + 1 - lead.length, body: [...lead, line] };
|
|
1727
|
+
lead = [];
|
|
1728
|
+
blocks.push(current);
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
if (/^#/.test(line)) {
|
|
1732
|
+
lead.push(line); // a note waiting for the key it describes
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
if (lead.length) { blocks.push({ key: null, body: lead }); lead = []; } // …that never came
|
|
1736
|
+
if (current && (line.trim() === '' || /^\s/.test(line))) {
|
|
1737
|
+
current.body.push(line);
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
blocks.push({ key: null, body: [line] }); // a fence-level line of some other shape
|
|
1741
|
+
current = null;
|
|
1742
|
+
}
|
|
1743
|
+
if (lead.length) blocks.push({ key: null, body: lead });
|
|
1744
|
+
|
|
1745
|
+
const moved = blocks.filter((block) => block.key && isMovableSection(block.key));
|
|
1746
|
+
if (!moved.length) return { text, edits: [], findings: [] }; // already migrated — idempotent
|
|
1747
|
+
|
|
1748
|
+
const kept = blocks.filter((block) => !moved.includes(block));
|
|
1749
|
+
const existing = kept.find((block) => block.key === 'config');
|
|
1750
|
+
|
|
1751
|
+
// Trailing blank lines belong BETWEEN blocks, not inside the moved one.
|
|
1752
|
+
const indented = moved.flatMap((block) => {
|
|
1753
|
+
while (block.body.length > 1 && block.body[block.body.length - 1].trim() === '') block.body.pop();
|
|
1754
|
+
return block.body.map((line) => (line.trim() === '' ? line : ` ${line}`));
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
if (existing) {
|
|
1758
|
+
existing.body.push(...indented);
|
|
1759
|
+
} else {
|
|
1760
|
+
kept.push({ key: 'config', body: ['config:', ...indented] });
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
const edits = moved.map((block) => ({
|
|
1764
|
+
rule: 'config-parent', line: block.line,
|
|
1765
|
+
before: `${block.key}:`, after: `config: ${block.key}:`,
|
|
1766
|
+
}));
|
|
1767
|
+
|
|
1768
|
+
return {
|
|
1769
|
+
text: fromLines([lines[0], ...kept.flatMap((block) => block.body), ...lines.slice(fenceEnd)], trailingNewline),
|
|
1770
|
+
edits,
|
|
1771
|
+
findings: [{
|
|
1772
|
+
check: 'config-parent', line: moved[0].line, severity: 'warning',
|
|
1773
|
+
message: `moved ${moved.map((block) => `\`${block.key}\``).join(', ')} under \`config:\` — a page overrides omega.json5 there now (#607), `
|
|
1774
|
+
+ 'and no config section keeps a bare spelling. Check the merged result renders what the page meant',
|
|
1775
|
+
}],
|
|
1776
|
+
};
|
|
1777
|
+
},
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
// ---------------------------------------------------------------------------
|
|
1781
|
+
// Rule 26 — the retired `append:` frontmatter flag
|
|
1782
|
+
// ([#607](https://github.com/Omega-JS-Stack/omega/issues/607), Ian 2026-08-26)
|
|
1783
|
+
//
|
|
1784
|
+
// `append: true` was the legacy UJM add-below contract kept behind a flag: the
|
|
1785
|
+
// page body rendered BELOW the layout's default `{% composition %}` instead of
|
|
1786
|
+
// replacing it. The flag is deleted — a page that wants the default bands
|
|
1787
|
+
// writes them (`omega customize <url>` materializes exactly that) — so a page
|
|
1788
|
+
// still carrying it renders the same as one without, and the key survives only
|
|
1789
|
+
// as an unknown frontmatter key the build strips with a warning. Dropped here,
|
|
1790
|
+
// with the finding that says what changed.
|
|
1791
|
+
//
|
|
1792
|
+
// Frontmatter-scoped and TOP-LEVEL only: `append` is a `| append:` Liquid
|
|
1793
|
+
// filter everywhere else, and an indented `append:` belongs to some other
|
|
1794
|
+
// block's data.
|
|
1795
|
+
// ---------------------------------------------------------------------------
|
|
1796
|
+
const appendFlag = {
|
|
1797
|
+
id: 'append-flag',
|
|
1798
|
+
title: 'the retired `append:` frontmatter flag → dropped',
|
|
1799
|
+
apply(text) {
|
|
1800
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1801
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
1802
|
+
if (fenceEnd < 0) return { text, edits: [], findings: [] };
|
|
1803
|
+
|
|
1804
|
+
const at = lines.findIndex((line, index) => index > 0 && index < fenceEnd && /^append:/.test(line));
|
|
1805
|
+
if (at < 0) return { text, edits: [], findings: [] }; // already migrated — idempotent
|
|
1806
|
+
|
|
1807
|
+
const out = [...lines];
|
|
1808
|
+
out.splice(at, 1);
|
|
1809
|
+
|
|
1810
|
+
return {
|
|
1811
|
+
text: fromLines(out, trailingNewline),
|
|
1812
|
+
edits: [{ rule: 'append-flag', line: at + 1, before: lines[at].trim(), after: '' }],
|
|
1813
|
+
findings: [{
|
|
1814
|
+
check: 'append-flag', line: at + 1, severity: 'warning',
|
|
1815
|
+
message: '`append: true` is gone (#607) — a page body REPLACES the layout\'s default composition, and there is no '
|
|
1816
|
+
+ 'flag to keep both. Run `omega customize <url>` to materialize the default bands into this page, then edit them '
|
|
1817
|
+
+ 'with your own content below',
|
|
1818
|
+
}],
|
|
1819
|
+
};
|
|
1820
|
+
},
|
|
1821
|
+
};
|
|
1822
|
+
|
|
1823
|
+
// ---------------------------------------------------------------------------
|
|
1824
|
+
// Rule 25 — the UJM `random_id` global → an `omega_random` assign
|
|
1825
|
+
// ([#595](https://github.com/Omega-JS-Stack/omega/issues/595))
|
|
1826
|
+
//
|
|
1827
|
+
// UJM handed every render a fresh `random_id`, and consumer includes scoped
|
|
1828
|
+
// repeated markup with it (`id="tool-faq-{{ random_id }}"`). OMEGA has no such
|
|
1829
|
+
// global, so the read renders EMPTY and every accordion on the page shares one
|
|
1830
|
+
// container id — silently. `omega_random` is the successor, and the idiom is
|
|
1831
|
+
// one assign at the top of the file, which the reads then keep spelling
|
|
1832
|
+
// exactly as they did.
|
|
1833
|
+
//
|
|
1834
|
+
// Runs LAST: it inserts a line at the top, so every line-numbered edit and
|
|
1835
|
+
// finding above it reports the pre-insert line.
|
|
1836
|
+
// ---------------------------------------------------------------------------
|
|
1837
|
+
const randomIdGlobal = {
|
|
1838
|
+
id: 'random-id',
|
|
1839
|
+
title: '`{{ random_id }}` (the UJM per-render global) → `{% assign random_id = 100 | omega_random %}`',
|
|
1840
|
+
apply(text) {
|
|
1841
|
+
const reads = randomIdReads(text);
|
|
1842
|
+
if (!reads.length || assignsRandomId(text)) return { text, edits: [], findings: [] }; // already migrated — idempotent
|
|
1843
|
+
|
|
1844
|
+
const { lines, trailingNewline } = toLines(text);
|
|
1845
|
+
// Under the frontmatter fence if there is one: an assign inside it is YAML,
|
|
1846
|
+
// not Liquid. A file with no fence (an include, the shape that carried this
|
|
1847
|
+
// idiom) takes line 1.
|
|
1848
|
+
const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
|
|
1849
|
+
const at = fenceEnd < 0 ? 0 : fenceEnd + 1;
|
|
1850
|
+
|
|
1851
|
+
const out = [...lines];
|
|
1852
|
+
out.splice(at, 0, RANDOM_ID_ASSIGN_IDIOM);
|
|
1853
|
+
|
|
1854
|
+
return {
|
|
1855
|
+
text: fromLines(out, trailingNewline),
|
|
1856
|
+
edits: [{ rule: 'random-id', line: at + 1, before: '', after: RANDOM_ID_ASSIGN_IDIOM }],
|
|
1857
|
+
findings: [{
|
|
1858
|
+
check: 'random-id', line: reads[0].line, severity: 'warning',
|
|
1859
|
+
message: `\`random_id\` was a UJM per-render GLOBAL and OMEGA has none — unported it renders empty and every id it scopes collides. `
|
|
1860
|
+
+ `Migrate wrote \`${RANDOM_ID_ASSIGN_IDIOM}\` at the top of the file; check the value's range suits the ids it builds`,
|
|
1861
|
+
}],
|
|
1862
|
+
};
|
|
1863
|
+
},
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1866
|
+
const RULES = [
|
|
1867
|
+
iconTagMarkup, // 27 — FIRST: the icon tag stops being a tag (#619), so
|
|
1868
|
+
// neither the uj_→omega_ rename nor the tag-arg
|
|
1869
|
+
// hoist may see one
|
|
1870
|
+
legacyPrefix, // 0 — must precede every rule that matches tag names
|
|
1871
|
+
pageResolved, // 1
|
|
1872
|
+
bracketLayout, // 2
|
|
1873
|
+
tagArgInterpolation, // 3
|
|
1874
|
+
parentloop, // 4
|
|
1875
|
+
includeSlash, // 5
|
|
1876
|
+
canonicalUrl, // 7 — must precede page-props
|
|
1877
|
+
pageProps, // 6
|
|
1878
|
+
analyticsShape, // 9
|
|
1879
|
+
clientMarkup, // 10
|
|
1880
|
+
clientFrontmatter, // 11
|
|
1881
|
+
includeThemePath, // 12 — must follow include-slash
|
|
1882
|
+
adunitSection, // 20 — must follow include-slash
|
|
1883
|
+
markdownIal, // 21 — must precede gradient-utilities: the class list
|
|
1884
|
+
// it writes is a class list like any other
|
|
1885
|
+
gradientUtilities, // 16
|
|
1886
|
+
assetPath, // 17
|
|
1887
|
+
configReads, // 24 — LAST of the text rewrites: it renames the roots
|
|
1888
|
+
// rules 9 and 12 answer to (`site.analytics.…`,
|
|
1889
|
+
// `themes/[ site.theme.id ]/…`), so it goes after
|
|
1890
|
+
// every rule that reads one
|
|
1891
|
+
appendFlag, // 26 — before the re-indenting rules: it deletes a
|
|
1892
|
+
// frontmatter line, so its own finding is the
|
|
1893
|
+
// pre-move line like every rule above
|
|
1894
|
+
sectionArgs, // 18 — the INSERTING rules go last, so every
|
|
1895
|
+
heroSecondaryButton, // 22 line-numbered finding above them reports the
|
|
1896
|
+
configParent, // 23 pre-rewrite line
|
|
1897
|
+
randomIdGlobal, // 25 — LAST: it inserts at the TOP, which moves every
|
|
1898
|
+
// other line in the file
|
|
1899
|
+
];
|
|
1900
|
+
|
|
1901
|
+
// The consumer-JS table (src/**/*.js — the tree the template walk skips).
|
|
1902
|
+
// Template rules never run over JS: `page.title` in a script is an object
|
|
1903
|
+
// property, not a Jekyll frontmatter read.
|
|
1904
|
+
const JS_RULES = [
|
|
1905
|
+
clientMarkup, // 10 — the same class/attribute names, spelled in JS
|
|
1906
|
+
clientImport, // 13 — must precede client-libs (it puts `omega` in scope)
|
|
1907
|
+
clientLibs, // 14
|
|
1908
|
+
ujLibrary, // 19
|
|
1909
|
+
serviceWorkerImport, // 15
|
|
1910
|
+
];
|
|
1911
|
+
|
|
1912
|
+
// The section-descriptor table (src/**/*.json — bindings and classes declared
|
|
1913
|
+
// as DATA, not markup). Only the rules whose subject is a LITERAL TOKEN belong
|
|
1914
|
+
// here: the page-props, include and frontmatter rules answer to template text,
|
|
1915
|
+
// and a `page.`-looking string in a descriptor is a JSON value, not a Jekyll
|
|
1916
|
+
// read. A `{{ … }}` binding is the exception — the descriptor emits it into the
|
|
1917
|
+
// page, so it is a read like any other (nav.json's logo text is the case).
|
|
1918
|
+
const JSON_RULES = [
|
|
1919
|
+
clientMarkup, // 10 — the same class/attribute names, spelled in a descriptor
|
|
1920
|
+
configReads, // 24 — a binding that emits config renders empty too (#611)
|
|
1921
|
+
];
|
|
1922
|
+
|
|
1923
|
+
module.exports = {
|
|
1924
|
+
RULES, JS_RULES, JSON_RULES,
|
|
1925
|
+
// Rule 24 alone, for the ONE file that is not a template and still carries
|
|
1926
|
+
// reads: the brand's own omega.json5 (#671, migrate/index.js step 2b).
|
|
1927
|
+
configReads,
|
|
1928
|
+
VALID_PAGE_PROPS, MANUAL_PAGE_PROPS, PACKAGED_THEMES,
|
|
1929
|
+
};
|